mirror of
https://github.com/rangermix/TwitchDropsMiner.git
synced 2026-05-26 07:08:04 +00:00
feat: enhance version validation with semver range support and test suite
- Add dual-mode version extraction supporting branch and file validation - Implement comprehensive semver range validation (caret, tilde, wildcards) - Extract release creation logic to dedicated script for reusability - Add test suite with comprehensive coverage for validation scripts - Update workflow to validate new version is greater than current - Bump version to 1.1.0 in pyproject.toml to match version.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
71
.github/scripts/create_release.sh
vendored
Executable file
71
.github/scripts/create_release.sh
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
# create_release.sh - Create release branch and tag for version release
|
||||
# Usage: create_release.sh <version> <current_branch>
|
||||
#
|
||||
# Arguments:
|
||||
# version: The validated semver version (e.g., 1.2.3 or 2.0.0-rc.1)
|
||||
# current_branch: The current branch name to push changes to
|
||||
#
|
||||
# This script:
|
||||
# 1. Updates src/version.py with the new version
|
||||
# 2. Updates pyproject.toml with the new version
|
||||
# 3. Commits and pushes changes to current branch
|
||||
# 4. Creates a new release branch
|
||||
# 5. Creates and pushes a version tag
|
||||
#
|
||||
# Expected to be called from GitHub Actions after version validation
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Check arguments
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <version> <current_branch>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="$1"
|
||||
CURRENT_BRANCH="$2"
|
||||
|
||||
echo "Creating release for version: $VERSION"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
|
||||
# Configure git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Update version.py
|
||||
echo "Updating src/version.py..."
|
||||
mv -f src/version.py src/last_version.py
|
||||
echo "__version__ = \"$VERSION\"" > src/version.py
|
||||
|
||||
# Update pyproject.toml
|
||||
echo "Updating pyproject.toml..."
|
||||
sed -i "s/^version = \"[^\"]*\"\(.*\)/version = \"$VERSION\"\1/" pyproject.toml
|
||||
|
||||
# Commit changes
|
||||
echo "Committing version changes..."
|
||||
git add src/version.py pyproject.toml
|
||||
git commit -m "chore: bump version to $VERSION"
|
||||
git push origin "$CURRENT_BRANCH"
|
||||
|
||||
NEW_BRANCH_NAME="release/$VERSION"
|
||||
|
||||
# Create and checkout new branch
|
||||
echo "Creating release branch: $NEW_BRANCH_NAME"
|
||||
git checkout -b "$NEW_BRANCH_NAME"
|
||||
|
||||
# Push branch (this will trigger the publish workflow)
|
||||
git push origin "$NEW_BRANCH_NAME"
|
||||
|
||||
# Create tag
|
||||
echo "Creating and pushing tag: v$VERSION"
|
||||
git tag "v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
|
||||
# Success messages
|
||||
echo ""
|
||||
echo "✅ Updated version to $VERSION"
|
||||
echo "✅ Created branch '$NEW_BRANCH_NAME'"
|
||||
echo "✅ Created and pushed tag v$VERSION"
|
||||
echo "✅ The publish workflow will now build Docker images and create the GitHub release"
|
||||
76
.github/scripts/extract_version.sh
vendored
76
.github/scripts/extract_version.sh
vendored
@@ -2,8 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Script: extract_version.sh
|
||||
# Description: Extracts version from branch name and validates against version.py
|
||||
# Usage: extract_version.sh <branch_name>
|
||||
# Description: Extracts and validates version from branch name and/or project files
|
||||
# Usage: extract_version.sh [branch_name]
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
@@ -12,44 +12,72 @@ NC='\033[0m' # No Color
|
||||
|
||||
# Display usage information
|
||||
usage() {
|
||||
echo "Usage: $0 <branch_name>"
|
||||
echo "Usage: $0 [branch_name]"
|
||||
echo ""
|
||||
echo "Extracts version from release branch name and validates it matches version.py."
|
||||
echo "Mode 1 (with branch name): Validates version from branch matches src/version.py and pyproject.toml"
|
||||
echo "Mode 2 (no branch name): Validates src/version.py and pyproject.toml versions match"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 release/1.2.3"
|
||||
echo " $0 release/2.0.0-rc.1"
|
||||
echo " $0 release/1.2.3 # Validate branch version matches files"
|
||||
echo " $0 release/2.0.0-rc.1 # Validate pre-release branch version"
|
||||
echo " $0 # Validate version.py and pyproject.toml match"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if branch name argument is provided
|
||||
if [ $# -eq 0 ]; then
|
||||
echo -e "${RED}Error: No branch name specified${NC}"
|
||||
usage
|
||||
fi
|
||||
|
||||
BRANCH_NAME="$1"
|
||||
|
||||
# Extract version from branch name (release/1.2.3 -> 1.2.3)
|
||||
VERSION="${BRANCH_NAME#release/}"
|
||||
echo "Branch version: $VERSION"
|
||||
|
||||
# Read version from version.py
|
||||
if [ ! -f "src/version.py" ]; then
|
||||
echo -e "${RED}Error: src/version.py not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILE_VERSION=$(grep -oP '__version__ = "\K[^"]+' src/version.py)
|
||||
echo "File version: $FILE_VERSION"
|
||||
VERSION_PY=$(grep -oP '__version__ = "\K[^"]+' src/version.py)
|
||||
echo "src/version.py version: $VERSION_PY"
|
||||
|
||||
# Verify they match
|
||||
if [ "$VERSION" != "$FILE_VERSION" ]; then
|
||||
echo -e "${RED}Error: Branch version ($VERSION) does not match version.py ($FILE_VERSION)${NC}"
|
||||
# Read version from pyproject.toml
|
||||
if [ ! -f "pyproject.toml" ]; then
|
||||
echo -e "${RED}Error: pyproject.toml not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Version match validated: $VERSION${NC}"
|
||||
VERSION_TOML=$(grep -oP '^version = "\K[^"]+' pyproject.toml)
|
||||
echo "pyproject.toml version: $VERSION_TOML"
|
||||
|
||||
# Check if branch name argument is provided
|
||||
if [ $# -eq 0 ]; then
|
||||
# Mode 2: No branch name - just validate files match
|
||||
echo ""
|
||||
echo "No branch name provided - validating file versions match..."
|
||||
|
||||
if [ "$VERSION_PY" != "$VERSION_TOML" ]; then
|
||||
echo -e "${RED}Error: Version mismatch between files:${NC}"
|
||||
echo -e "${RED} src/version.py: $VERSION_PY${NC}"
|
||||
echo -e "${RED} pyproject.toml: $VERSION_TOML${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Version files match: $VERSION_PY${NC}"
|
||||
VERSION="$VERSION_PY"
|
||||
else
|
||||
# Mode 1: Branch name provided - validate all three
|
||||
BRANCH_NAME="$1"
|
||||
|
||||
# Extract version from branch name (release/1.2.3 -> 1.2.3)
|
||||
BRANCH_VERSION="${BRANCH_NAME#release/}"
|
||||
echo "Branch version: $BRANCH_VERSION"
|
||||
echo ""
|
||||
|
||||
# Check all three versions match
|
||||
if [ "$BRANCH_VERSION" != "$VERSION_PY" ] || [ "$BRANCH_VERSION" != "$VERSION_TOML" ]; then
|
||||
echo -e "${RED}Error: Version mismatch detected:${NC}"
|
||||
echo -e "${RED} Branch: $BRANCH_VERSION${NC}"
|
||||
echo -e "${RED} src/version.py: $VERSION_PY${NC}"
|
||||
echo -e "${RED} pyproject.toml: $VERSION_TOML${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ All versions match: $BRANCH_VERSION${NC}"
|
||||
VERSION="$BRANCH_VERSION"
|
||||
fi
|
||||
|
||||
# Output to GITHUB_OUTPUT if available
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
|
||||
187
.github/scripts/test/README.md
vendored
Normal file
187
.github/scripts/test/README.md
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
# Script Tests
|
||||
|
||||
This directory contains test suites for the scripts in `.github/scripts/`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run All Tests
|
||||
|
||||
Run all test suites:
|
||||
|
||||
```bash
|
||||
# Run semver validation tests
|
||||
.github/scripts/test/test_validate_semver.sh
|
||||
|
||||
# Run GitHub Actions output tests
|
||||
.github/scripts/test/test_github_output.sh
|
||||
```
|
||||
|
||||
### Test validate_semver.sh
|
||||
|
||||
Run the comprehensive test suite for the SemVer validation script:
|
||||
|
||||
```bash
|
||||
.github/scripts/test/test_validate_semver.sh
|
||||
```
|
||||
|
||||
The test suite includes **98 test cases** covering:
|
||||
|
||||
1. **Basic SemVer Format Validation** (18 tests)
|
||||
- Valid stable versions (1.2.3, 0.0.0, etc.)
|
||||
- Valid pre-release versions (1.0.0-alpha, 1.0.0-rc.1, etc.)
|
||||
- Valid build metadata (1.0.0+build, 1.0.0-beta+exp.sha.5114f85)
|
||||
- Invalid formats (missing components, leading zeros, non-numeric)
|
||||
|
||||
2. **Comparison Operators** (14 tests)
|
||||
- Greater than or equal (`>=`)
|
||||
- Greater than (`>`)
|
||||
- Less than or equal (`<=`)
|
||||
- Less than (`<`)
|
||||
- Equal (`=`)
|
||||
|
||||
3. **Caret Ranges** (13 tests)
|
||||
- Standard caret ranges (`^2.0.0`)
|
||||
- Caret with 0.x versions (`^0.2.3`)
|
||||
- Caret with 0.0.x versions (`^0.0.3`)
|
||||
- Pre-release handling
|
||||
|
||||
4. **Tilde Ranges** (8 tests)
|
||||
- Standard tilde ranges (`~1.2.0`)
|
||||
- Tilde with different versions
|
||||
|
||||
5. **Wildcard Ranges** (14 tests)
|
||||
- Major wildcards (`1.x`, `2.X`, `3.*`)
|
||||
- Minor wildcards (`1.5.x`)
|
||||
- Full wildcards (`1.x.x`)
|
||||
- Pre-release with wildcards
|
||||
|
||||
6. **Compound Ranges** (10 tests)
|
||||
- AND ranges (`>=1.0.0 <2.0.0`)
|
||||
- Multiple constraints (`>1.0.0 <=2.0.0`)
|
||||
|
||||
7. **Pre-release Version Comparisons** (8 tests)
|
||||
- Pre-release ordering
|
||||
- Pre-release with ranges
|
||||
|
||||
8. **Build Metadata** (4 tests)
|
||||
- Build metadata is ignored in comparisons
|
||||
|
||||
9. **Edge Cases** (9 tests)
|
||||
- Boundary conditions
|
||||
- Large version numbers
|
||||
- Zero versions
|
||||
|
||||
## Test Output
|
||||
|
||||
The test script provides colorized output:
|
||||
|
||||
- ✓ (green) - Test passed
|
||||
- ✗ (red) - Test failed
|
||||
- Blue section headers
|
||||
- Summary statistics at the end
|
||||
|
||||
Example output:
|
||||
|
||||
```text
|
||||
Running validate_semver.sh tests...
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Test 1: Basic SemVer Format Validation
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
✓ Valid stable version: 1.2.3
|
||||
✓ Valid stable version: 0.0.0
|
||||
...
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Test Results Summary
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Total tests run: 98
|
||||
Tests passed: 98
|
||||
Tests failed: 0
|
||||
|
||||
✓ All tests passed!
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- `0` - All tests passed
|
||||
- `1` - One or more tests failed
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add new test cases, edit [test_validate_semver.sh](test_validate_semver.sh) and use the `run_test` helper function:
|
||||
|
||||
```bash
|
||||
run_test "test description" "version" "range" "should_pass"
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `test_description`: Human-readable test name
|
||||
- `version`: The semver version to test
|
||||
- `range`: The range to validate against (optional, use `""` for none)
|
||||
- `should_pass`: `"true"` if test should pass, `"false"` if it should fail
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
run_test "1.2.3 >= 1.0.0" "1.2.3" ">=1.0.0" "true"
|
||||
run_test "1.2.3 >= 2.0.0" "1.2.3" ">=2.0.0" "false"
|
||||
```
|
||||
|
||||
### Test GITHUB_OUTPUT Functionality
|
||||
|
||||
Run tests specifically for GitHub Actions output integration:
|
||||
|
||||
```bash
|
||||
.github/scripts/test/test_github_output.sh
|
||||
```
|
||||
|
||||
The GITHUB_OUTPUT test suite includes **25 test cases** covering:
|
||||
|
||||
1. **Basic Output Without Range** (4 tests)
|
||||
- Stable version outputs
|
||||
- Large version numbers
|
||||
|
||||
2. **Pre-release Version Output** (4 tests)
|
||||
- Alpha, beta, rc versions
|
||||
- Complex pre-release identifiers
|
||||
|
||||
3. **Build Metadata in Output** (3 tests)
|
||||
- Verifies build metadata is preserved in output
|
||||
- Stable and pre-release with metadata
|
||||
|
||||
4. **Range Validation Output** (5 tests)
|
||||
- Output when version satisfies ranges
|
||||
- Tests `range_satisfied` field
|
||||
|
||||
5. **Pre-release with Range Output** (2 tests)
|
||||
- Pre-release versions with range constraints
|
||||
|
||||
6. **Edge Cases** (3 tests)
|
||||
- Boundary conditions
|
||||
- Complex compound ranges
|
||||
|
||||
7. **Output File Format Validation** (3 tests)
|
||||
- Validates key=value format
|
||||
- Correct number of output lines (2 without range, 3 with range)
|
||||
|
||||
8. **No Output When GITHUB_OUTPUT Not Set** (1 test)
|
||||
- Ensures no file created when env var not set
|
||||
|
||||
**Output Fields:**
|
||||
|
||||
- `version` - The validated semver version
|
||||
- `is_prerelease` - Boolean (`true`/`false`) indicating if version is pre-release
|
||||
- `range_satisfied` - Boolean (`true`/`false`) indicating if version satisfies range (only when range provided)
|
||||
|
||||
## CI Integration
|
||||
|
||||
These tests can be integrated into CI/CD pipelines:
|
||||
|
||||
```yaml
|
||||
- name: Run script tests
|
||||
run: |
|
||||
.github/scripts/test/test_validate_semver.sh
|
||||
.github/scripts/test/test_github_output.sh
|
||||
```
|
||||
419
.github/scripts/test/test_github_output.sh
vendored
Executable file
419
.github/scripts/test/test_github_output.sh
vendored
Executable file
@@ -0,0 +1,419 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Test script for validate_semver.sh GitHub Actions output functionality
|
||||
# Usage: ./test_github_output.sh
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get the directory of this test script
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT_DIR="$(dirname "$TEST_DIR")"
|
||||
SCRIPT="$SCRIPT_DIR/validate_semver.sh"
|
||||
|
||||
# Test counters
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
# Create temp directory for test outputs
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
|
||||
# Test helper function
|
||||
test_github_output() {
|
||||
local test_name="$1"
|
||||
local version="$2"
|
||||
local range="${3:-}"
|
||||
local expected_version="$4"
|
||||
local expected_is_prerelease="$5"
|
||||
local expected_range_satisfied="${6:-}"
|
||||
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
|
||||
# Create a temporary output file
|
||||
local output_file="$TEMP_DIR/github_output_${TESTS_RUN}.txt"
|
||||
export GITHUB_OUTPUT="$output_file"
|
||||
|
||||
# Run the script
|
||||
local result=0
|
||||
if [ -n "$range" ]; then
|
||||
bash "$SCRIPT" "$version" "$range" &>/dev/null || result=$?
|
||||
else
|
||||
bash "$SCRIPT" "$version" &>/dev/null || result=$?
|
||||
fi
|
||||
|
||||
# Check if script succeeded when it should
|
||||
if [ $result -ne 0 ]; then
|
||||
echo -e "${RED}✗${NC} $test_name (script failed with exit code $result)"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
unset GITHUB_OUTPUT
|
||||
return
|
||||
fi
|
||||
|
||||
# Parse the output file
|
||||
local actual_version=""
|
||||
local actual_is_prerelease=""
|
||||
local actual_range_satisfied=""
|
||||
|
||||
if [ -f "$output_file" ]; then
|
||||
while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
version)
|
||||
actual_version="$value"
|
||||
;;
|
||||
is_prerelease)
|
||||
actual_is_prerelease="$value"
|
||||
;;
|
||||
range_satisfied)
|
||||
actual_range_satisfied="$value"
|
||||
;;
|
||||
esac
|
||||
done < "$output_file"
|
||||
fi
|
||||
|
||||
# Validate outputs
|
||||
local test_passed=true
|
||||
local error_msg=""
|
||||
|
||||
if [ "$actual_version" != "$expected_version" ]; then
|
||||
test_passed=false
|
||||
error_msg="${error_msg}version: expected '$expected_version', got '$actual_version'; "
|
||||
fi
|
||||
|
||||
if [ "$actual_is_prerelease" != "$expected_is_prerelease" ]; then
|
||||
test_passed=false
|
||||
error_msg="${error_msg}is_prerelease: expected '$expected_is_prerelease', got '$actual_is_prerelease'; "
|
||||
fi
|
||||
|
||||
if [ -n "$expected_range_satisfied" ] && [ "$actual_range_satisfied" != "$expected_range_satisfied" ]; then
|
||||
test_passed=false
|
||||
error_msg="${error_msg}range_satisfied: expected '$expected_range_satisfied', got '$actual_range_satisfied'; "
|
||||
fi
|
||||
|
||||
# Report results
|
||||
if [ "$test_passed" = true ]; then
|
||||
echo -e "${GREEN}✓${NC} $test_name"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name"
|
||||
echo -e " ${RED}${error_msg}${NC}"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
|
||||
unset GITHUB_OUTPUT
|
||||
}
|
||||
|
||||
# Print test section header
|
||||
section() {
|
||||
echo ""
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
}
|
||||
|
||||
# Start tests
|
||||
echo -e "${YELLOW}Testing GITHUB_OUTPUT functionality...${NC}"
|
||||
|
||||
# ============================================================================
|
||||
# Test 1: Basic Output Without Range
|
||||
# ============================================================================
|
||||
section "Test 1: Basic Output Without Range"
|
||||
|
||||
test_github_output \
|
||||
"Stable version output" \
|
||||
"1.2.3" \
|
||||
"" \
|
||||
"1.2.3" \
|
||||
"false"
|
||||
|
||||
test_github_output \
|
||||
"Another stable version" \
|
||||
"2.5.8" \
|
||||
"" \
|
||||
"2.5.8" \
|
||||
"false"
|
||||
|
||||
test_github_output \
|
||||
"Zero version output" \
|
||||
"0.0.0" \
|
||||
"" \
|
||||
"0.0.0" \
|
||||
"false"
|
||||
|
||||
test_github_output \
|
||||
"Large version numbers" \
|
||||
"100.200.300" \
|
||||
"" \
|
||||
"100.200.300" \
|
||||
"false"
|
||||
|
||||
# ============================================================================
|
||||
# Test 2: Pre-release Version Output
|
||||
# ============================================================================
|
||||
section "Test 2: Pre-release Version Output"
|
||||
|
||||
test_github_output \
|
||||
"Pre-release alpha" \
|
||||
"1.0.0-alpha" \
|
||||
"" \
|
||||
"1.0.0-alpha" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Pre-release beta" \
|
||||
"2.0.0-beta.1" \
|
||||
"" \
|
||||
"2.0.0-beta.1" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Pre-release rc" \
|
||||
"3.1.0-rc.2" \
|
||||
"" \
|
||||
"3.1.0-rc.2" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Complex pre-release" \
|
||||
"1.0.0-alpha.beta.1" \
|
||||
"" \
|
||||
"1.0.0-alpha.beta.1" \
|
||||
"true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 3: Build Metadata (should be included in version output)
|
||||
# ============================================================================
|
||||
section "Test 3: Build Metadata in Output"
|
||||
|
||||
test_github_output \
|
||||
"Stable with build metadata" \
|
||||
"1.2.3+build.123" \
|
||||
"" \
|
||||
"1.2.3+build.123" \
|
||||
"false"
|
||||
|
||||
test_github_output \
|
||||
"Pre-release with build metadata" \
|
||||
"1.0.0-beta+exp.sha.5114f85" \
|
||||
"" \
|
||||
"1.0.0-beta+exp.sha.5114f85" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Build metadata only" \
|
||||
"2.0.0+20130313144700" \
|
||||
"" \
|
||||
"2.0.0+20130313144700" \
|
||||
"false"
|
||||
|
||||
# ============================================================================
|
||||
# Test 4: Range Validation Output
|
||||
# ============================================================================
|
||||
section "Test 4: Range Validation Output"
|
||||
|
||||
test_github_output \
|
||||
"Version satisfying >= range" \
|
||||
"1.2.3" \
|
||||
">=1.0.0" \
|
||||
"1.2.3" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Version satisfying caret range" \
|
||||
"2.5.0" \
|
||||
"^2.0.0" \
|
||||
"2.5.0" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Version satisfying tilde range" \
|
||||
"1.2.5" \
|
||||
"~1.2.0" \
|
||||
"1.2.5" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Version satisfying wildcard range" \
|
||||
"1.5.2" \
|
||||
"1.x" \
|
||||
"1.5.2" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Version satisfying compound range" \
|
||||
"3.1.4" \
|
||||
">=1.0.0 <4.0.0" \
|
||||
"3.1.4" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 5: Pre-release with Range
|
||||
# ============================================================================
|
||||
section "Test 5: Pre-release with Range Output"
|
||||
|
||||
test_github_output \
|
||||
"Pre-release satisfying range" \
|
||||
"1.2.3-beta.1" \
|
||||
">=1.0.0" \
|
||||
"1.2.3-beta.1" \
|
||||
"true" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Pre-release with caret range" \
|
||||
"2.0.1-alpha" \
|
||||
"^2.0.0" \
|
||||
"2.0.1-alpha" \
|
||||
"true" \
|
||||
"true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 6: Edge Cases
|
||||
# ============================================================================
|
||||
section "Test 6: Edge Cases"
|
||||
|
||||
test_github_output \
|
||||
"Boundary version with >= range" \
|
||||
"1.0.0" \
|
||||
">=1.0.0" \
|
||||
"1.0.0" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Zero version with range" \
|
||||
"0.0.0" \
|
||||
">=0.0.0" \
|
||||
"0.0.0" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
test_github_output \
|
||||
"Complex version with complex range" \
|
||||
"1.5.0" \
|
||||
">1.0.0 <=2.0.0" \
|
||||
"1.5.0" \
|
||||
"false" \
|
||||
"true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 7: Verify Output File Format
|
||||
# ============================================================================
|
||||
section "Test 7: Output File Format Validation"
|
||||
|
||||
# Test that the output file has the correct format
|
||||
output_file="$TEMP_DIR/format_test.txt"
|
||||
export GITHUB_OUTPUT="$output_file"
|
||||
|
||||
bash "$SCRIPT" "1.2.3" ">=1.0.0" &>/dev/null
|
||||
|
||||
if [ -f "$output_file" ]; then
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
|
||||
# Check that file contains key=value format
|
||||
format_valid=true
|
||||
while IFS= read -r line; do
|
||||
if ! echo "$line" | grep -q '^[a-z_]*=.*$'; then
|
||||
format_valid=false
|
||||
break
|
||||
fi
|
||||
done < "$output_file"
|
||||
|
||||
if [ "$format_valid" = true ]; then
|
||||
echo -e "${GREEN}✓${NC} Output file format is valid (key=value)"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} Output file format is invalid"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
|
||||
# Check that file has exactly 3 lines when range is provided
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
line_count=$(wc -l < "$output_file")
|
||||
if [ "$line_count" -eq 3 ]; then
|
||||
echo -e "${GREEN}✓${NC} Output has correct number of lines (3) with range"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} Output has $line_count lines, expected 3"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} Output file was not created"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 2))
|
||||
TESTS_RUN=$((TESTS_RUN + 2))
|
||||
fi
|
||||
|
||||
unset GITHUB_OUTPUT
|
||||
|
||||
# Test without range (should have 2 lines)
|
||||
output_file="$TEMP_DIR/format_test_no_range.txt"
|
||||
export GITHUB_OUTPUT="$output_file"
|
||||
|
||||
bash "$SCRIPT" "1.2.3" &>/dev/null
|
||||
|
||||
if [ -f "$output_file" ]; then
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
line_count=$(wc -l < "$output_file")
|
||||
if [ "$line_count" -eq 2 ]; then
|
||||
echo -e "${GREEN}✓${NC} Output has correct number of lines (2) without range"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} Output has $line_count lines, expected 2"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
unset GITHUB_OUTPUT
|
||||
|
||||
# ============================================================================
|
||||
# Test 8: No Output When GITHUB_OUTPUT Not Set
|
||||
# ============================================================================
|
||||
section "Test 8: No Output When GITHUB_OUTPUT Not Set"
|
||||
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
|
||||
# Make sure GITHUB_OUTPUT is not set
|
||||
unset GITHUB_OUTPUT
|
||||
|
||||
# Run script
|
||||
bash "$SCRIPT" "1.2.3" &>/dev/null
|
||||
|
||||
# There should be no github_output file created in current directory
|
||||
if [ ! -f "github_output" ] && [ ! -f "${GITHUB_OUTPUT:-}" ]; then
|
||||
echo -e "${GREEN}✓${NC} No output file created when GITHUB_OUTPUT not set"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} Output file was created when GITHUB_OUTPUT not set"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Test Results Summary
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BLUE}Test Results Summary${NC}"
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "Total tests run: ${YELLOW}$TESTS_RUN${NC}"
|
||||
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
|
||||
echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All GITHUB_OUTPUT tests passed!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ Some GITHUB_OUTPUT tests failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
286
.github/scripts/test/test_validate_semver.sh
vendored
Executable file
286
.github/scripts/test/test_validate_semver.sh
vendored
Executable file
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Test script for validate_semver.sh
|
||||
# Usage: ./test_validate_semver.sh
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get the directory of this test script
|
||||
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT_DIR="$(dirname "$TEST_DIR")"
|
||||
SCRIPT="$SCRIPT_DIR/validate_semver.sh"
|
||||
|
||||
# Test counters
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
# Test helper functions
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
local version="$2"
|
||||
local range="${3:-}"
|
||||
local should_pass="$4"
|
||||
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
|
||||
local result=0
|
||||
if [ -n "$range" ]; then
|
||||
bash "$SCRIPT" "$version" "$range" &>/dev/null || result=$?
|
||||
else
|
||||
bash "$SCRIPT" "$version" &>/dev/null || result=$?
|
||||
fi
|
||||
|
||||
if [ "$should_pass" = "true" ]; then
|
||||
if [ $result -eq 0 ]; then
|
||||
echo -e "${GREEN}✓${NC} $test_name"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name (expected pass, got fail)"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
else
|
||||
if [ $result -ne 0 ]; then
|
||||
echo -e "${GREEN}✓${NC} $test_name"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name (expected fail, got pass)"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Print test section header
|
||||
section() {
|
||||
echo ""
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
}
|
||||
|
||||
# Start tests
|
||||
echo -e "${YELLOW}Running validate_semver.sh tests...${NC}"
|
||||
|
||||
# ============================================================================
|
||||
# Test 1: Basic SemVer Format Validation
|
||||
# ============================================================================
|
||||
section "Test 1: Basic SemVer Format Validation"
|
||||
|
||||
run_test "Valid stable version: 1.2.3" "1.2.3" "" "true"
|
||||
run_test "Valid stable version: 0.0.0" "0.0.0" "" "true"
|
||||
run_test "Valid stable version: 10.20.30" "10.20.30" "" "true"
|
||||
run_test "Valid pre-release: 1.0.0-alpha" "1.0.0-alpha" "" "true"
|
||||
run_test "Valid pre-release: 1.0.0-alpha.1" "1.0.0-alpha.1" "" "true"
|
||||
run_test "Valid pre-release: 1.0.0-0.3.7" "1.0.0-0.3.7" "" "true"
|
||||
run_test "Valid pre-release: 1.0.0-x.7.z.92" "1.0.0-x.7.z.92" "" "true"
|
||||
run_test "Valid with build metadata: 1.0.0+20130313144700" "1.0.0+20130313144700" "" "true"
|
||||
run_test "Valid with build metadata: 1.0.0-beta+exp.sha.5114f85" "1.0.0-beta+exp.sha.5114f85" "" "true"
|
||||
run_test "Valid complex: 1.0.0-alpha.beta+build.1" "1.0.0-alpha.beta+build.1" "" "true"
|
||||
|
||||
run_test "Invalid: missing patch" "1.2" "" "false"
|
||||
run_test "Invalid: missing minor and patch" "1" "" "false"
|
||||
run_test "Invalid: non-numeric major" "v1.2.3" "" "false"
|
||||
run_test "Invalid: leading zeros" "01.2.3" "" "false"
|
||||
run_test "Invalid: leading zeros in minor" "1.02.3" "" "false"
|
||||
run_test "Invalid: leading zeros in patch" "1.2.03" "" "false"
|
||||
run_test "Invalid: empty string" "" "" "false"
|
||||
run_test "Invalid: random text" "not-a-version" "" "false"
|
||||
|
||||
# ============================================================================
|
||||
# Test 2: Comparison Operators
|
||||
# ============================================================================
|
||||
section "Test 2: Comparison Operators (>=, >, <=, <, =)"
|
||||
|
||||
# Greater than or equal (>=)
|
||||
run_test "1.2.3 >= 1.0.0" "1.2.3" ">=1.0.0" "true"
|
||||
run_test "1.2.3 >= 1.2.3" "1.2.3" ">=1.2.3" "true"
|
||||
run_test "1.2.3 >= 2.0.0" "1.2.3" ">=2.0.0" "false"
|
||||
|
||||
# Greater than (>)
|
||||
run_test "2.0.0 > 1.9.9" "2.0.0" ">1.9.9" "true"
|
||||
run_test "1.2.3 > 1.2.3" "1.2.3" ">1.2.3" "false"
|
||||
run_test "1.0.0 > 2.0.0" "1.0.0" ">2.0.0" "false"
|
||||
|
||||
# Less than or equal (<=)
|
||||
run_test "1.0.0 <= 2.0.0" "1.0.0" "<=2.0.0" "true"
|
||||
run_test "1.2.3 <= 1.2.3" "1.2.3" "<=1.2.3" "true"
|
||||
run_test "3.0.0 <= 2.0.0" "3.0.0" "<=2.0.0" "false"
|
||||
|
||||
# Less than (<)
|
||||
run_test "1.0.0 < 2.0.0" "1.0.0" "<2.0.0" "true"
|
||||
run_test "1.2.3 < 1.2.3" "1.2.3" "<1.2.3" "false"
|
||||
run_test "3.0.0 < 2.0.0" "3.0.0" "<2.0.0" "false"
|
||||
|
||||
# Equal (=)
|
||||
run_test "1.2.3 = 1.2.3" "1.2.3" "=1.2.3" "true"
|
||||
run_test "1.2.3 = 1.2.4" "1.2.3" "=1.2.4" "false"
|
||||
|
||||
# ============================================================================
|
||||
# Test 3: Caret Ranges (^)
|
||||
# ============================================================================
|
||||
section "Test 3: Caret Ranges (^)"
|
||||
|
||||
# Standard caret ranges
|
||||
run_test "2.5.0 satisfies ^2.0.0" "2.5.0" "^2.0.0" "true"
|
||||
run_test "2.0.0 satisfies ^2.0.0" "2.0.0" "^2.0.0" "true"
|
||||
run_test "2.9.9 satisfies ^2.0.0" "2.9.9" "^2.0.0" "true"
|
||||
run_test "3.0.0 does not satisfy ^2.0.0" "3.0.0" "^2.0.0" "false"
|
||||
run_test "1.9.9 does not satisfy ^2.0.0" "1.9.9" "^2.0.0" "false"
|
||||
|
||||
# Caret with 0.x versions
|
||||
run_test "0.2.5 satisfies ^0.2.3" "0.2.5" "^0.2.3" "true"
|
||||
run_test "0.2.3 satisfies ^0.2.3" "0.2.3" "^0.2.3" "true"
|
||||
run_test "0.3.0 does not satisfy ^0.2.3" "0.3.0" "^0.2.3" "false"
|
||||
run_test "0.2.2 does not satisfy ^0.2.3" "0.2.2" "^0.2.3" "false"
|
||||
|
||||
# Caret with 0.0.x versions
|
||||
run_test "0.0.3 satisfies ^0.0.3" "0.0.3" "^0.0.3" "true"
|
||||
run_test "0.0.4 does not satisfy ^0.0.3" "0.0.4" "^0.0.3" "false"
|
||||
|
||||
# Caret with pre-release
|
||||
run_test "2.0.0-rc.1 does not satisfy ^2.0.0" "2.0.0-rc.1" "^2.0.0" "false"
|
||||
run_test "2.0.1 satisfies ^2.0.0" "2.0.1" "^2.0.0" "true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 4: Tilde Ranges (~)
|
||||
# ============================================================================
|
||||
section "Test 4: Tilde Ranges (~)"
|
||||
|
||||
# Standard tilde ranges
|
||||
run_test "1.2.9 satisfies ~1.2.0" "1.2.9" "~1.2.0" "true"
|
||||
run_test "1.2.0 satisfies ~1.2.0" "1.2.0" "~1.2.0" "true"
|
||||
run_test "1.3.0 does not satisfy ~1.2.0" "1.3.0" "~1.2.0" "false"
|
||||
run_test "1.1.9 does not satisfy ~1.2.0" "1.1.9" "~1.2.0" "false"
|
||||
|
||||
# Tilde with different versions
|
||||
run_test "2.5.8 satisfies ~2.5.0" "2.5.8" "~2.5.0" "true"
|
||||
run_test "2.6.0 does not satisfy ~2.5.0" "2.6.0" "~2.5.0" "false"
|
||||
|
||||
run_test "0.1.5 satisfies ~0.1.0" "0.1.5" "~0.1.0" "true"
|
||||
run_test "0.2.0 does not satisfy ~0.1.0" "0.2.0" "~0.1.0" "false"
|
||||
|
||||
# ============================================================================
|
||||
# Test 5: Wildcard Ranges (x, X, *)
|
||||
# ============================================================================
|
||||
section "Test 5: Wildcard Ranges (x, X, *)"
|
||||
|
||||
# Major wildcard
|
||||
run_test "1.5.2 satisfies 1.x" "1.5.2" "1.x" "true"
|
||||
run_test "1.0.0 satisfies 1.x" "1.0.0" "1.x" "true"
|
||||
run_test "1.9.9 satisfies 1.x" "1.9.9" "1.x" "true"
|
||||
run_test "2.0.0 does not satisfy 1.x" "2.0.0" "1.x" "false"
|
||||
|
||||
# Major wildcard with X
|
||||
run_test "2.3.4 satisfies 2.X" "2.3.4" "2.X" "true"
|
||||
run_test "3.0.0 does not satisfy 2.X" "3.0.0" "2.X" "false"
|
||||
|
||||
# Major wildcard with *
|
||||
run_test "3.1.2 satisfies 3.*" "3.1.2" "3.*" "true"
|
||||
run_test "4.0.0 does not satisfy 3.*" "4.0.0" "3.*" "false"
|
||||
|
||||
# Minor wildcard
|
||||
run_test "1.5.2 satisfies 1.5.x" "1.5.2" "1.5.x" "true"
|
||||
run_test "1.5.0 satisfies 1.5.x" "1.5.0" "1.5.x" "true"
|
||||
run_test "1.6.0 does not satisfy 1.5.x" "1.6.0" "1.5.x" "false"
|
||||
|
||||
# Full wildcard
|
||||
run_test "1.2.3 satisfies 1.x.x" "1.2.3" "1.x.x" "true"
|
||||
run_test "1.5.8 satisfies 1.x.x" "1.5.8" "1.x.x" "true"
|
||||
|
||||
# Wildcard with prerelease
|
||||
run_test "1.5.2-beta satisfies 1.x" "1.5.2-beta" "1.x" "true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 6: Compound Ranges
|
||||
# ============================================================================
|
||||
section "Test 6: Compound Ranges (Multiple Constraints)"
|
||||
|
||||
# AND ranges
|
||||
run_test "1.5.0 satisfies >=1.0.0 <2.0.0" "1.5.0" ">=1.0.0 <2.0.0" "true"
|
||||
run_test "2.0.0 does not satisfy >=1.0.0 <2.0.0" "2.0.0" ">=1.0.0 <2.0.0" "false"
|
||||
run_test "0.9.9 does not satisfy >=1.0.0 <2.0.0" "0.9.9" ">=1.0.0 <2.0.0" "false"
|
||||
|
||||
run_test "3.1.4 satisfies >=1.0.0 <4.0.0" "3.1.4" ">=1.0.0 <4.0.0" "true"
|
||||
run_test "4.0.0 does not satisfy >=1.0.0 <4.0.0" "4.0.0" ">=1.0.0 <4.0.0" "false"
|
||||
|
||||
run_test "1.5.0 satisfies >1.0.0 <=2.0.0" "1.5.0" ">1.0.0 <=2.0.0" "true"
|
||||
run_test "1.0.0 does not satisfy >1.0.0 <=2.0.0" "1.0.0" ">1.0.0 <=2.0.0" "false"
|
||||
run_test "2.0.0 satisfies >1.0.0 <=2.0.0" "2.0.0" ">1.0.0 <=2.0.0" "true"
|
||||
|
||||
# Multiple constraints
|
||||
run_test "2.5.0 satisfies >=2.0.0 <=3.0.0" "2.5.0" ">=2.0.0 <=3.0.0" "true"
|
||||
run_test "2.5.0 satisfies >2.0.0 <3.0.0" "2.5.0" ">2.0.0 <3.0.0" "true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 7: Pre-release Version Comparisons
|
||||
# ============================================================================
|
||||
section "Test 7: Pre-release Version Comparisons"
|
||||
|
||||
# Pre-release comparisons
|
||||
run_test "2.0.0-alpha satisfies >=2.0.0-alpha" "2.0.0-alpha" ">=2.0.0-alpha" "true"
|
||||
run_test "2.0.0-beta satisfies >2.0.0-alpha" "2.0.0-beta" ">2.0.0-alpha" "true"
|
||||
run_test "2.0.0-alpha satisfies <2.0.0" "2.0.0-alpha" "<2.0.0" "true"
|
||||
run_test "2.0.0 satisfies >2.0.0-alpha" "2.0.0" ">2.0.0-alpha" "true"
|
||||
|
||||
run_test "1.0.0-rc.1 satisfies >=1.0.0-rc.1" "1.0.0-rc.1" ">=1.0.0-rc.1" "true"
|
||||
run_test "1.0.0-rc.2 satisfies >1.0.0-rc.1" "1.0.0-rc.2" ">1.0.0-rc.1" "true"
|
||||
|
||||
# Pre-release with ranges
|
||||
run_test "1.2.3-beta.1 satisfies >=1.0.0" "1.2.3-beta.1" ">=1.0.0" "true"
|
||||
run_test "1.2.3-beta.1 satisfies >=1.2.3-alpha" "1.2.3-beta.1" ">=1.2.3-alpha" "true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 8: Build Metadata
|
||||
# ============================================================================
|
||||
section "Test 8: Build Metadata (should be ignored in comparisons)"
|
||||
|
||||
run_test "1.2.3+build.1 = 1.2.3" "1.2.3+build.1" "=1.2.3" "true"
|
||||
run_test "1.2.3+build.1 = 1.2.3+build.2" "1.2.3+build.1" "=1.2.3+build.2" "true"
|
||||
run_test "1.2.3+build satisfies >=1.2.0" "1.2.3+build" ">=1.2.0" "true"
|
||||
run_test "1.0.0-beta+exp.sha.5114f85 satisfies <1.0.0" "1.0.0-beta+exp.sha.5114f85" "<1.0.0" "true"
|
||||
|
||||
# ============================================================================
|
||||
# Test 9: Edge Cases
|
||||
# ============================================================================
|
||||
section "Test 9: Edge Cases"
|
||||
|
||||
# Boundary conditions
|
||||
run_test "1.0.0 satisfies >=1.0.0" "1.0.0" ">=1.0.0" "true"
|
||||
run_test "1.0.0 does not satisfy >1.0.0" "1.0.0" ">1.0.0" "false"
|
||||
run_test "1.0.0 satisfies <=1.0.0" "1.0.0" "<=1.0.0" "true"
|
||||
run_test "1.0.0 does not satisfy <1.0.0" "1.0.0" "<1.0.0" "false"
|
||||
|
||||
# Large version numbers
|
||||
run_test "100.200.300 satisfies >=1.0.0" "100.200.300" ">=1.0.0" "true"
|
||||
run_test "999.999.999 satisfies ^999.0.0" "999.999.999" "^999.0.0" "true"
|
||||
|
||||
# Zero versions
|
||||
run_test "0.0.0 satisfies >=0.0.0" "0.0.0" ">=0.0.0" "true"
|
||||
run_test "0.1.0 satisfies >0.0.0" "0.1.0" ">0.0.0" "true"
|
||||
run_test "0.0.1 satisfies ^0.0.1" "0.0.1" "^0.0.1" "true"
|
||||
|
||||
# ============================================================================
|
||||
# Test Results Summary
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BLUE}Test Results Summary${NC}"
|
||||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "Total tests run: ${YELLOW}$TESTS_RUN${NC}"
|
||||
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
|
||||
echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All tests passed!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ Some tests failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
259
.github/scripts/validate_semver.sh
vendored
259
.github/scripts/validate_semver.sh
vendored
@@ -13,17 +13,255 @@ NC='\033[0m' # No Color
|
||||
|
||||
# Display usage information
|
||||
usage() {
|
||||
echo "Usage: $0 <version>"
|
||||
echo "Usage: $0 <version> [range]"
|
||||
echo ""
|
||||
echo "Validates a version string against SemVer specification."
|
||||
echo "Optionally validates that the version satisfies a semver range."
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 1.2.3"
|
||||
echo " $0 2.0.0-rc.1"
|
||||
echo " $0 1.0.0-beta.2+build.123"
|
||||
echo ""
|
||||
echo "Range validation examples:"
|
||||
echo " $0 1.2.3 '>=1.0.0' # Greater than or equal"
|
||||
echo " $0 2.5.0 '^2.0.0' # Caret range (compatible with 2.x.x)"
|
||||
echo " $0 1.2.5 '~1.2.0' # Tilde range (compatible with 1.2.x)"
|
||||
echo " $0 3.1.4 '>=1.0.0 <4.0.0' # Compound range"
|
||||
echo " $0 1.5.2 '1.x' # Wildcard range"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse version string into components
|
||||
# Usage: parse_version <version>
|
||||
# Outputs: major minor patch prerelease
|
||||
parse_version() {
|
||||
local version="$1"
|
||||
# Remove build metadata
|
||||
version="${version%%+*}"
|
||||
|
||||
# Extract prerelease if present
|
||||
local prerelease=""
|
||||
if [[ "$version" =~ - ]]; then
|
||||
prerelease="${version#*-}"
|
||||
version="${version%%-*}"
|
||||
fi
|
||||
|
||||
# Parse major.minor.patch
|
||||
local major="${version%%.*}"
|
||||
local rest="${version#*.}"
|
||||
local minor="${rest%%.*}"
|
||||
local patch="${rest#*.}"
|
||||
|
||||
echo "$major $minor $patch $prerelease"
|
||||
}
|
||||
|
||||
# Compare two versions
|
||||
# Returns: 0 if v1 == v2, 1 if v1 > v2, -1 if v1 < v2
|
||||
compare_versions() {
|
||||
local v1="$1"
|
||||
local v2="$2"
|
||||
|
||||
read -r maj1 min1 pat1 pre1 <<< "$(parse_version "$v1")"
|
||||
read -r maj2 min2 pat2 pre2 <<< "$(parse_version "$v2")"
|
||||
|
||||
# Compare major
|
||||
if [ "$maj1" -gt "$maj2" ]; then echo 1; return; fi
|
||||
if [ "$maj1" -lt "$maj2" ]; then echo -1; return; fi
|
||||
|
||||
# Compare minor
|
||||
if [ "$min1" -gt "$min2" ]; then echo 1; return; fi
|
||||
if [ "$min1" -lt "$min2" ]; then echo -1; return; fi
|
||||
|
||||
# Compare patch
|
||||
if [ "$pat1" -gt "$pat2" ]; then echo 1; return; fi
|
||||
if [ "$pat1" -lt "$pat2" ]; then echo -1; return; fi
|
||||
|
||||
# Compare prerelease (version with prerelease < version without)
|
||||
if [ -z "$pre1" ] && [ -n "$pre2" ]; then echo 1; return; fi
|
||||
if [ -n "$pre1" ] && [ -z "$pre2" ]; then echo -1; return; fi
|
||||
|
||||
# Both have prerelease or both don't - lexicographic comparison
|
||||
if [ "$pre1" \> "$pre2" ]; then echo 1; return; fi
|
||||
if [ "$pre1" \< "$pre2" ]; then echo -1; return; fi
|
||||
|
||||
echo 0
|
||||
}
|
||||
|
||||
# Check if version satisfies a single range constraint
|
||||
# Usage: check_constraint <version> <operator> <target>
|
||||
check_constraint() {
|
||||
local version="$1"
|
||||
local operator="$2"
|
||||
local target="$3"
|
||||
local cmp
|
||||
|
||||
cmp=$(compare_versions "$version" "$target")
|
||||
|
||||
case "$operator" in
|
||||
"="|"")
|
||||
[ "$cmp" -eq 0 ]
|
||||
;;
|
||||
">")
|
||||
[ "$cmp" -eq 1 ]
|
||||
;;
|
||||
">=")
|
||||
[ "$cmp" -eq 1 ] || [ "$cmp" -eq 0 ]
|
||||
;;
|
||||
"<")
|
||||
[ "$cmp" -eq -1 ]
|
||||
;;
|
||||
"<=")
|
||||
[ "$cmp" -eq -1 ] || [ "$cmp" -eq 0 ]
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if version satisfies caret range (^)
|
||||
# ^1.2.3 := >=1.2.3 <2.0.0
|
||||
# ^0.2.3 := >=0.2.3 <0.3.0
|
||||
# ^0.0.3 := >=0.0.3 <0.0.4
|
||||
check_caret_range() {
|
||||
local version="$1"
|
||||
local target="$2"
|
||||
|
||||
read -r maj min pat pre <<< "$(parse_version "$target")"
|
||||
|
||||
# Must be >= target
|
||||
if ! check_constraint "$version" ">=" "$target"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Determine upper bound based on leftmost non-zero component
|
||||
local upper
|
||||
if [ "$maj" != "0" ]; then
|
||||
upper="$((maj + 1)).0.0"
|
||||
elif [ "$min" != "0" ]; then
|
||||
upper="0.$((min + 1)).0"
|
||||
else
|
||||
upper="0.0.$((pat + 1))"
|
||||
fi
|
||||
|
||||
check_constraint "$version" "<" "$upper"
|
||||
}
|
||||
|
||||
# Check if version satisfies tilde range (~)
|
||||
# ~1.2.3 := >=1.2.3 <1.3.0
|
||||
# ~1.2 := >=1.2.0 <1.3.0
|
||||
check_tilde_range() {
|
||||
local version="$1"
|
||||
local target="$2"
|
||||
|
||||
read -r maj min pat pre <<< "$(parse_version "$target")"
|
||||
|
||||
# Must be >= target
|
||||
if ! check_constraint "$version" ">=" "$target"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Upper bound is next minor version
|
||||
local upper="$maj.$((min + 1)).0"
|
||||
|
||||
check_constraint "$version" "<" "$upper"
|
||||
}
|
||||
|
||||
# Check if version matches wildcard pattern
|
||||
# 1.x, 1.X, 1.*, 1.2.x, etc.
|
||||
check_wildcard() {
|
||||
local version="$1"
|
||||
local pattern="$2"
|
||||
|
||||
read -r v_maj v_min v_pat v_pre <<< "$(parse_version "$version")"
|
||||
|
||||
# Normalize shorthand patterns: "1.x" -> "1.x.x", "1" -> "1.x.x"
|
||||
local dot_count
|
||||
dot_count=$(echo "$pattern" | tr -cd '.' | wc -c)
|
||||
|
||||
if [ "$dot_count" -eq 0 ]; then
|
||||
# Just "1" or "1x" -> "1.x.x"
|
||||
pattern="${pattern}.x.x"
|
||||
elif [ "$dot_count" -eq 1 ]; then
|
||||
# "1.2" or "1.x" -> "1.2.x" or "1.x.x"
|
||||
pattern="${pattern}.x"
|
||||
fi
|
||||
|
||||
# Replace wildcards with a placeholder that won't conflict
|
||||
pattern="${pattern//x/__WILDCARD__}"
|
||||
pattern="${pattern//X/__WILDCARD__}"
|
||||
pattern="${pattern//\*/__WILDCARD__}"
|
||||
|
||||
# Escape dots
|
||||
pattern="${pattern//./\\.}"
|
||||
|
||||
# Replace placeholders with regex pattern for any number
|
||||
pattern="${pattern//__WILDCARD__/[0-9]+}"
|
||||
|
||||
# Add start/end anchors and optional prerelease/build metadata
|
||||
pattern="^${pattern}(-.*)?(\+.*)?$"
|
||||
|
||||
echo "$version" | grep -Pq "$pattern"
|
||||
}
|
||||
|
||||
# Check if version satisfies a range expression
|
||||
satisfies_range() {
|
||||
local version="$1"
|
||||
local range="$2"
|
||||
|
||||
# Handle caret range
|
||||
if [[ "$range" =~ ^\^(.+)$ ]]; then
|
||||
check_caret_range "$version" "${BASH_REMATCH[1]}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Handle tilde range
|
||||
if [[ "$range" =~ ^~(.+)$ ]]; then
|
||||
check_tilde_range "$version" "${BASH_REMATCH[1]}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Handle wildcard
|
||||
if [[ "$range" =~ [xX*] ]]; then
|
||||
check_wildcard "$version" "$range"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Handle compound ranges (space-separated)
|
||||
if [[ "$range" =~ [[:space:]] ]]; then
|
||||
local all_satisfied=true
|
||||
local constraint
|
||||
|
||||
# Split on spaces and process each constraint
|
||||
while read -r constraint; do
|
||||
[ -z "$constraint" ] && continue
|
||||
|
||||
if ! satisfies_range "$version" "$constraint"; then
|
||||
all_satisfied=false
|
||||
break
|
||||
fi
|
||||
done <<< "$(echo "$range" | tr ' ' '\n')"
|
||||
|
||||
[ "$all_satisfied" = true ]
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Handle simple operator ranges (>=, >, <=, <, =)
|
||||
if [[ "$range" =~ ^(>=|>|<=|<|=)?(.+)$ ]]; then
|
||||
local operator="${BASH_REMATCH[1]}"
|
||||
local target="${BASH_REMATCH[2]}"
|
||||
|
||||
# Default to = if no operator
|
||||
[ -z "$operator" ] && operator="="
|
||||
|
||||
check_constraint "$version" "$operator" "$target"
|
||||
return $?
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if version argument is provided
|
||||
if [ $# -eq 0 ]; then
|
||||
echo -e "${RED}Error: No version specified${NC}"
|
||||
@@ -31,6 +269,7 @@ if [ $# -eq 0 ]; then
|
||||
fi
|
||||
|
||||
VERSION="$1"
|
||||
RANGE="${2:-}"
|
||||
|
||||
# SemVer regex from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
||||
SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$'
|
||||
@@ -48,6 +287,19 @@ if ! echo "$VERSION" | grep -Pq "$SEMVER_REGEX"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate range if provided
|
||||
if [ -n "$RANGE" ]; then
|
||||
if satisfies_range "$VERSION" "$RANGE"; then
|
||||
echo -e "${GREEN}✓ Version '$VERSION' satisfies range '$RANGE'${NC}"
|
||||
RANGE_SATISFIED=true
|
||||
else
|
||||
echo -e "${RED}Error: Version '$VERSION' does not satisfy range '$RANGE'${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
RANGE_SATISFIED=false
|
||||
fi
|
||||
|
||||
# Determine release type
|
||||
if echo "$VERSION" | grep -q '-'; then
|
||||
echo -e "${YELLOW}✓ Detected pre-release version: $VERSION${NC}"
|
||||
@@ -66,6 +318,11 @@ if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
else
|
||||
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Output range validation result if range was provided
|
||||
if [ -n "$RANGE" ]; then
|
||||
echo "range_satisfied=$RANGE_SATISFIED" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
50
.github/workflows/version-release.yml
vendored
50
.github/workflows/version-release.yml
vendored
@@ -26,18 +26,13 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.PUBLISHER_TOKEN }}
|
||||
|
||||
- name: Validate version format
|
||||
id: validate
|
||||
run: .github/scripts/validate_semver.sh "${{ github.event.inputs.version }}"
|
||||
- name: Extract version
|
||||
id: extract
|
||||
run: .github/scripts/extract_version.sh
|
||||
|
||||
- name: Check if input version
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep -oP '(?<=__version__ = ")[^"]+' src/version.py)
|
||||
INPUT_VERSION="${{ github.event.inputs.version }}"
|
||||
if [ "$CURRENT_VERSION" = "$INPUT_VERSION" ]; then
|
||||
echo "Error: Input version '$INPUT_VERSION' is the same as the current version"
|
||||
exit 1
|
||||
fi
|
||||
- name: Validate version format and range
|
||||
id: validate
|
||||
run: .github/scripts/validate_semver.sh "${{ github.event.inputs.version }}" ">${{ steps.extract.outputs.version }}"
|
||||
|
||||
- name: Check if branch exists
|
||||
run: |
|
||||
@@ -51,35 +46,4 @@ jobs:
|
||||
echo "Branch '$BRANCH_NAME' does not exist, proceeding..."
|
||||
|
||||
- name: Create release branch and update version
|
||||
run: |
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
VERSION="${{ steps.validate.outputs.version }}"
|
||||
|
||||
# Update version.py
|
||||
mv -f src/version.py src/last_version.py
|
||||
echo "__version__ = \"$VERSION\"" > src/version.py
|
||||
|
||||
# Commit changes
|
||||
git add src/version.py
|
||||
git commit -m "chore: bump version to $VERSION"
|
||||
git push origin $CURRENT_BRANCH_NAME
|
||||
|
||||
BRANCH_NAME="release/${{ steps.validate.outputs.version }}"
|
||||
|
||||
# Create and checkout new branch
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
# Push branch (this will trigger the publish workflow)
|
||||
git push origin "$BRANCH_NAME"
|
||||
|
||||
# Create tag
|
||||
git tag "v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
|
||||
echo "✅ Updated version to $VERSION"
|
||||
echo "✅ Created branch '$BRANCH_NAME'"
|
||||
echo "✅ Created and pushed tag v$VERSION"
|
||||
echo "✅ The publish workflow will now build Docker images and create the GitHub release"
|
||||
run: .github/scripts/create_release.sh "${{ steps.validate.outputs.version }}" "$CURRENT_BRANCH_NAME"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "twitch-drops-miner"
|
||||
version = "1.0.0" # Update from src/version.py as needed
|
||||
version = "1.1.0" # Update from src/version.py as needed
|
||||
description = "Automatically mine Twitch drops without downloading stream data"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
Reference in New Issue
Block a user