From de070aae64cb490c78fa310f4f45c8f5801da259 Mon Sep 17 00:00:00 2001 From: Annurdien Rasyid Date: Tue, 22 Jul 2025 13:03:56 +0700 Subject: [PATCH 1/6] feat: enhance Makefile with additional test targets and clean commands; add CI/CD workflows for quality and dependency updates --- .github/ISSUE_TEMPLATE/bug_report.md | 32 +++++ .github/ISSUE_TEMPLATE/feature_request.md | 25 ++++ .github/pull_request_template.md | 40 ++++++ .github/workflows/ci.yml | 160 ++++++++++++++++++++++ .github/workflows/dependencies.yml | 48 +++++++ .github/workflows/quality.yml | 93 +++++++++++++ .golangci.yml | 142 +++++++++++++++++++ Makefile | 50 +++++-- 8 files changed, 582 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dependencies.yml create mode 100644 .github/workflows/quality.yml create mode 100644 .golangci.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9e31f22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[BUG] ' +labels: 'bug' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run command '...' +2. Use device '...' +3. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots/Output** +If applicable, add screenshots or command output to help explain your problem. + +**Environment (please complete the following information):** + - OS: [e.g. macOS 14.0, Ubuntu 22.04, Windows 11] + - Go version: [e.g. 1.21.0] + - sim-cli version: [e.g. v1.0.0] + - Device type: [e.g. iOS Simulator, Android Emulator] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6dcdc71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: 'enhancement' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +**Platform considerations** +- [ ] iOS Simulator +- [ ] Android Emulator +- [ ] Cross-platform compatibility needed diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d461c9c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,40 @@ +## Description +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. + +Fixes # (issue) + +## Type of change +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +## How Has This Been Tested? +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. + +- [ ] Unit tests pass +- [ ] Integration tests pass +- [ ] Manual testing performed + +**Test Configuration**: +- OS: [e.g. macOS, Linux, Windows] +- Go version: [e.g. 1.21.0] +- Device types tested: [e.g. iOS Simulator, Android Emulator] + +## Checklist: +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Screenshots (if applicable): +Please add screenshots to help explain your changes. + +## Additional Notes: +Add any other notes about the pull request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5acb6ed --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,160 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + release: + types: [published] + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + go-version: [1.23.x, 1.24.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Run go vet + run: go vet ./... + + - name: Run tests + run: go test -race -coverprofile=coverage.out -covermode=atomic ./... + + - name: Upload coverage to Codecov + if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.24.x' + uses: codecov/codecov-action@v4 + with: + file: ./coverage.out + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + args: --timeout=5m + + build: + name: Build + runs-on: ubuntu-latest + needs: [test, lint] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Build application + run: make build + + - name: Build for multiple platforms + run: make build-all + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: sim-cli-binaries + path: dist/ + + release: + name: Release + runs-on: ubuntu-latest + needs: [test, lint, build] + if: github.event_name == 'release' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Build for release + run: make build-all + + - name: Create checksums + run: | + cd dist + sha256sum * > checksums.txt + + - name: Upload release assets + uses: softprops/action-gh-release@v1 + with: + files: | + dist/sim-darwin-amd64 + dist/sim-darwin-arm64 + dist/sim-linux-amd64 + dist/sim-windows-amd64.exe + dist/checksums.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + security: + name: Security Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Run Gosec Security Scanner + uses: securecodewarrior/github-action-gosec@master + with: + args: '-no-fail -fmt sarif -out results.sarif ./...' + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 0000000..dbef5df --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,48 @@ +name: Dependency Update + +on: + schedule: + # Run every Monday at 9 AM UTC + - cron: '0 9 * * 1' + workflow_dispatch: # Allow manual triggering + +jobs: + update-dependencies: + name: Update Go Dependencies + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Update dependencies + run: | + go get -u ./... + go mod tidy + + - name: Run tests + run: go test ./... + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: update Go dependencies' + title: 'chore: update Go dependencies' + body: | + This PR updates Go dependencies to their latest versions. + + - Updated all dependencies using `go get -u ./...` + - Ran `go mod tidy` to clean up module files + - All tests pass with updated dependencies + + Please review the changes before merging. + branch: dependency-updates + delete-branch: true diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..8f99f7f --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,93 @@ +name: Code Quality + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + quality: + name: Code Quality Analysis + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Download dependencies + run: go mod download + + - name: Run tests with coverage + run: go test -race -coverprofile=coverage.out -covermode=atomic ./... + + - name: Generate coverage report + run: go tool cover -html=coverage.out -o coverage.html + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + coverage.out + coverage.html + + - name: Check test coverage + run: | + COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}') + echo "Total test coverage: $COVERAGE%" + if (( $(echo "$COVERAGE < 70" | bc -l) )); then + echo "❌ Test coverage is below 70%" + exit 1 + else + echo "✅ Test coverage is above 70%" + fi + + - name: Run go mod verify + run: go mod verify + + - name: Check for vulnerabilities + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + - name: Check for outdated dependencies + run: | + go install github.com/psampaz/go-mod-outdated@latest + go list -u -m -json all | go-mod-outdated -update -direct + + performance: + name: Performance Benchmarks + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Run benchmarks + run: | + if go test -list . | grep -q "Benchmark"; then + go test -bench=. -benchmem ./... > benchmark.txt + cat benchmark.txt + else + echo "No benchmarks found" + fi + + - name: Upload benchmark results + if: success() + uses: actions/upload-artifact@v4 + with: + name: benchmark-results + path: benchmark.txt diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d754ecc --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,142 @@ +run: + timeout: 5m + tests: true + skip-dirs: + - vendor + +linters: + enable: + - asciicheck + - bodyclose + - cyclop + - dupl + - durationcheck + - errcheck + - errorlint + - exhaustive + - exportloopref + - forbidigo + - forcetypeassert + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - gofmt + - gofumpt + - goheader + - goimports + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - importas + - ineffassign + - lll + - makezero + - misspell + - nakedret + - nestif + - nilerr + - nlreturn + - noctx + - nolintlint + - prealloc + - predeclared + - revive + - staticcheck + - stylecheck + - tagliatelle + - tenv + - testpackage + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - wastedassign + - whitespace + - wrapcheck + +linters-settings: + cyclop: + max-complexity: 15 + + gocognit: + min-complexity: 15 + + gocyclo: + min-complexity: 15 + + goconst: + min-len: 3 + min-occurrences: 3 + + godot: + capital: true + + gomnd: + settings: + mnd: + checks: + - argument + - case + - condition + - operation + - return + + lll: + line-length: 120 + + nakedret: + max-func-lines: 30 + + nestif: + min-complexity: 5 + + nlreturn: + block-size: 3 + + revive: + rules: + - name: exported + arguments: + - checkPrivateReceivers + - sayRepetitiveInsteadOfStutters + - name: unused-parameter + - name: unreachable-code + - name: constant-logical-expr + +issues: + exclude-rules: + # Disable some linters for test files + - path: "_test\\.go" + linters: + - gochecknoglobals + - gochecknoinits + - gomnd + - lll + - dupl + + # Disable gomnd for main.go as it often contains setup constants + - path: "main\\.go" + linters: + - gomnd + + # Disable some linters for cmd package (CLI commands often have many constants) + - path: "cmd/" + linters: + - gomnd + - gochecknoglobals + + max-same-issues: 0 + max-issues-per-linter: 0 diff --git a/Makefile b/Makefile index fcbf069..1c9056a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build clean install test help +.PHONY: build clean install test test-race test-coverage lint fmt vet check help # Default target all: build @@ -9,6 +9,7 @@ build: # Build for multiple platforms build-all: + mkdir -p dist GOOS=darwin GOARCH=amd64 go build -o dist/sim-darwin-amd64 GOOS=darwin GOARCH=arm64 go build -o dist/sim-darwin-arm64 GOOS=linux GOARCH=amd64 go build -o dist/sim-linux-amd64 @@ -18,6 +19,7 @@ build-all: clean: rm -f sim rm -rf dist/ + rm -f coverage.out coverage.html # Install dependencies deps: @@ -28,6 +30,32 @@ deps: test: go test ./... +# Run tests with race detection +test-race: + go test -race ./... + +# Run tests with coverage +test-coverage: + go test -race -coverprofile=coverage.out -covermode=atomic ./... + go tool cover -html=coverage.out -o coverage.html + go tool cover -func=coverage.out + +# Run linter +lint: + golangci-lint run + +# Format code +fmt: + go fmt ./... + goimports -w . + +# Run go vet +vet: + go vet ./... + +# Run all checks (used in CI) +check: fmt vet lint test-race + # Install to system (requires appropriate permissions) install: build cp sim /usr/local/bin/ @@ -35,10 +63,16 @@ install: build # Show help help: @echo "Available targets:" - @echo " build - Build the application" - @echo " build-all - Build for multiple platforms" - @echo " clean - Clean build artifacts" - @echo " deps - Install dependencies" - @echo " test - Run tests" - @echo " install - Install to /usr/local/bin" - @echo " help - Show this help" \ No newline at end of file + @echo " build - Build the application" + @echo " build-all - Build for multiple platforms" + @echo " clean - Clean build artifacts" + @echo " deps - Install dependencies" + @echo " test - Run tests" + @echo " test-race - Run tests with race detection" + @echo " test-coverage - Run tests with coverage report" + @echo " lint - Run golangci-lint" + @echo " fmt - Format code" + @echo " vet - Run go vet" + @echo " check - Run all checks (fmt, vet, lint, test-race)" + @echo " install - Install to /usr/local/bin" + @echo " help - Show this help" \ No newline at end of file From d5f339009d6cd89e5e1d249143b313a19088b9b2 Mon Sep 17 00:00:00 2001 From: Annurdien Rasyid Date: Tue, 22 Jul 2025 13:13:43 +0700 Subject: [PATCH 2/6] feat: update CI/CD workflows by removing outdated dependency update job and adding a release pipeline --- .github/workflows/ci.yml | 132 ++------------------------- .github/workflows/dependencies.yml | 48 ---------- .github/workflows/release.yml | 140 +++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 171 deletions(-) delete mode 100644 .github/workflows/dependencies.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5acb6ed..3cc6b7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,21 +1,15 @@ -name: CI/CD Pipeline +name: CI Pipeline on: push: - branches: [ main, develop ] + branches: [ main ] pull_request: - branches: [ main, develop ] - release: - types: [published] + branches: [ main ] jobs: - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - go-version: [1.23.x, 1.24.x] + ci: + name: CI + runs-on: ubuntu-latest steps: - name: Checkout code @@ -24,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ matrix.go-version }} + go-version: 1.24.x - name: Cache Go modules uses: actions/cache@v4 @@ -39,122 +33,14 @@ jobs: - name: Download dependencies run: go mod download - - name: Verify dependencies - run: go mod verify - - - name: Run go vet - run: go vet ./... - - - name: Run tests - run: go test -race -coverprofile=coverage.out -covermode=atomic ./... - - - name: Upload coverage to Codecov - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.24.x' - uses: codecov/codecov-action@v4 - with: - file: ./coverage.out - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false - - lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - name: Run golangci-lint uses: golangci/golangci-lint-action@v4 with: version: latest args: --timeout=5m - build: - name: Build - runs-on: ubuntu-latest - needs: [test, lint] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x + - name: Run tests + run: go test ./... - name: Build application run: make build - - - name: Build for multiple platforms - run: make build-all - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: sim-cli-binaries - path: dist/ - - release: - name: Release - runs-on: ubuntu-latest - needs: [test, lint, build] - if: github.event_name == 'release' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - - name: Build for release - run: make build-all - - - name: Create checksums - run: | - cd dist - sha256sum * > checksums.txt - - - name: Upload release assets - uses: softprops/action-gh-release@v1 - with: - files: | - dist/sim-darwin-amd64 - dist/sim-darwin-arm64 - dist/sim-linux-amd64 - dist/sim-windows-amd64.exe - dist/checksums.txt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - security: - name: Security Scan - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - - name: Run Gosec Security Scanner - uses: securecodewarrior/github-action-gosec@master - with: - args: '-no-fail -fmt sarif -out results.sarif ./...' - - - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: results.sarif diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml deleted file mode 100644 index dbef5df..0000000 --- a/.github/workflows/dependencies.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Dependency Update - -on: - schedule: - # Run every Monday at 9 AM UTC - - cron: '0 9 * * 1' - workflow_dispatch: # Allow manual triggering - -jobs: - update-dependencies: - name: Update Go Dependencies - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - - name: Update dependencies - run: | - go get -u ./... - go mod tidy - - - name: Run tests - run: go test ./... - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore: update Go dependencies' - title: 'chore: update Go dependencies' - body: | - This PR updates Go dependencies to their latest versions. - - - Updated all dependencies using `go get -u ./...` - - Ran `go mod tidy` to clean up module files - - All tests pass with updated dependencies - - Please review the changes before merging. - branch: dependency-updates - delete-branch: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9f9741c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,140 @@ +name: Release Pipeline + +on: + push: + branches: [ main ] + +jobs: + check-release: + name: Check Release Trigger + runs-on: ubuntu-latest + outputs: + should-release: ${{ steps.check.outputs.should-release }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check if commit message contains 'release:' + id: check + run: | + COMMIT_MSG=$(git log -1 --pretty=%B) + if [[ "$COMMIT_MSG" == *"release:"* ]]; then + echo "should-release=true" >> $GITHUB_OUTPUT + echo "Release commit detected: $COMMIT_MSG" + else + echo "should-release=false" >> $GITHUB_OUTPUT + echo "Not a release commit: $COMMIT_MSG" + fi + + release: + name: Release + runs-on: ${{ matrix.os }} + needs: check-release + if: needs.check-release.outputs.should-release == 'true' + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + args: --timeout=5m + + - name: Run tests + run: go test ./... + + - name: Build application + run: make build + + - name: Build for multiple platforms + run: make build-all + + - name: Create checksums (Linux only) + if: matrix.os == 'ubuntu-latest' + run: | + cd dist + sha256sum * > checksums.txt + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: sim-cli-binaries-${{ matrix.os }} + path: | + dist/ + + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [check-release, release] + if: needs.check-release.outputs.should-release == 'true' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Prepare release files + run: | + mkdir -p release-files + # Copy binaries from Linux build + cp artifacts/sim-cli-binaries-ubuntu-latest/dist/* release-files/ 2>/dev/null || true + # Copy binaries from macOS build (if different) + cp artifacts/sim-cli-binaries-macos-latest/dist/* release-files/ 2>/dev/null || true + ls -la release-files/ + + - name: Extract version from commit message + id: version + run: | + COMMIT_MSG=$(git log -1 --pretty=%B) + VERSION=$(echo "$COMMIT_MSG" | grep -o 'release:[[:space:]]*v[0-9]\+\.[0-9]\+\.[0-9]\+' | sed 's/release:[[:space:]]*//' || echo "v$(date +%Y%m%d-%H%M%S)") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.version }} + name: Release ${{ steps.version.outputs.version }} + body: | + Release created from commit: ${{ github.sha }} + + ## Changes + ${{ github.event.head_commit.message }} + + ## Downloads + - `sim-darwin-amd64`: macOS Intel + - `sim-darwin-arm64`: macOS Apple Silicon + - `sim-linux-amd64`: Linux x64 + files: | + release-files/* + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e4f85c62e0a64f61df1244956ac3c6accb00f31a Mon Sep 17 00:00:00 2001 From: Annurdien Rasyid Date: Tue, 22 Jul 2025 14:30:30 +0700 Subject: [PATCH 3/6] feat: update CI/CD workflows and improve code quality; adjust permissions and refactor commands --- .github/workflows/ci.yml | 2 +- .github/workflows/quality.yml | 93 ----------------- .golangci.yml | 182 +++++++++++++++++----------------- cmd/config.go | 8 +- cmd/device.go | 27 +++-- cmd/list.go | 4 +- cmd/media.go | 10 +- tests/config_test.go | 14 +-- tests/device_test.go | 6 +- tests/integration_test.go | 6 +- tests/list_test.go | 2 +- tests/media_test.go | 10 +- tests/test_utils.go | 109 ++++++++++---------- 13 files changed, 209 insertions(+), 264 deletions(-) delete mode 100644 .github/workflows/quality.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cc6b7f..461cbc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: args: --timeout=5m - name: Run tests - run: go test ./... + run: go test ./tests/ - name: Build application run: make build diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml deleted file mode 100644 index 8f99f7f..0000000 --- a/.github/workflows/quality.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Code Quality - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -jobs: - quality: - name: Code Quality Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - - name: Download dependencies - run: go mod download - - - name: Run tests with coverage - run: go test -race -coverprofile=coverage.out -covermode=atomic ./... - - - name: Generate coverage report - run: go tool cover -html=coverage.out -o coverage.html - - - name: Upload coverage artifacts - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: | - coverage.out - coverage.html - - - name: Check test coverage - run: | - COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}') - echo "Total test coverage: $COVERAGE%" - if (( $(echo "$COVERAGE < 70" | bc -l) )); then - echo "❌ Test coverage is below 70%" - exit 1 - else - echo "✅ Test coverage is above 70%" - fi - - - name: Run go mod verify - run: go mod verify - - - name: Check for vulnerabilities - run: | - go install golang.org/x/vuln/cmd/govulncheck@latest - govulncheck ./... - - - name: Check for outdated dependencies - run: | - go install github.com/psampaz/go-mod-outdated@latest - go list -u -m -json all | go-mod-outdated -update -direct - - performance: - name: Performance Benchmarks - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - - name: Run benchmarks - run: | - if go test -list . | grep -q "Benchmark"; then - go test -bench=. -benchmem ./... > benchmark.txt - cat benchmark.txt - else - echo "No benchmarks found" - fi - - - name: Upload benchmark results - if: success() - uses: actions/upload-artifact@v4 - with: - name: benchmark-results - path: benchmark.txt diff --git a/.golangci.yml b/.golangci.yml index d754ecc..9b80c59 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,9 +1,6 @@ +version: "2" run: - timeout: 5m tests: true - skip-dirs: - - vendor - linters: enable: - asciicheck @@ -11,13 +8,11 @@ linters: - cyclop - dupl - durationcheck - - errcheck + - err113 - errorlint - exhaustive - - exportloopref - forbidigo - forcetypeassert - - gci - gochecknoglobals - gochecknoinits - gocognit @@ -26,23 +21,16 @@ linters: - gocyclo - godot - godox - - goerr113 - - gofmt - - gofumpt - goheader - - goimports - - gomnd - gomoddirectives - gomodguard - goprintffuncname - gosec - - gosimple - - govet - importas - - ineffassign - lll - makezero - misspell + - mnd - nakedret - nestif - nilerr @@ -53,90 +41,104 @@ linters: - predeclared - revive - staticcheck - - stylecheck - tagliatelle - - tenv - testpackage - thelper - tparallel - - typecheck - unconvert - unparam - - unused - wastedassign - whitespace - wrapcheck - -linters-settings: - cyclop: - max-complexity: 15 - - gocognit: - min-complexity: 15 - - gocyclo: - min-complexity: 15 - - goconst: - min-len: 3 - min-occurrences: 3 - - godot: - capital: true - - gomnd: - settings: - mnd: - checks: - - argument - - case - - condition - - operation - - return - - lll: - line-length: 120 - - nakedret: - max-func-lines: 30 - - nestif: - min-complexity: 5 - - nlreturn: - block-size: 3 - - revive: + settings: + cyclop: + max-complexity: 15 + gocognit: + min-complexity: 15 + goconst: + min-len: 3 + min-occurrences: 3 + gocyclo: + min-complexity: 15 + godot: + capital: true + lll: + line-length: 120 + nakedret: + max-func-lines: 30 + nestif: + min-complexity: 5 + nlreturn: + block-size: 3 + revive: + rules: + - name: exported + arguments: + - checkPrivateReceivers + - sayRepetitiveInsteadOfStutters + - name: unused-parameter + - name: unreachable-code + - name: constant-logical-expr + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling rules: - - name: exported - arguments: - - checkPrivateReceivers - - sayRepetitiveInsteadOfStutters - - name: unused-parameter - - name: unreachable-code - - name: constant-logical-expr - + - linters: + - dupl + - gochecknoglobals + - gochecknoinits + - lll + - mnd + path: _test\.go + - linters: + - mnd + path: main\.go + - linters: + - gochecknoglobals + - mnd + - gochecknoinits + - forbidigo + - gocognit + - goconst + - revive + - wrapcheck + - gosec + - lll + - nestif + path: cmd/ + - linters: + - gci + path: cmd/device.go + - linters: + - testpackage + - goconst + - gocognit + - thelper + - wrapcheck + - revive + - gosec + - unparam + path: tests/ + paths: + - third_party$ + - builtin$ + - examples$ issues: - exclude-rules: - # Disable some linters for test files - - path: "_test\\.go" - linters: - - gochecknoglobals - - gochecknoinits - - gomnd - - lll - - dupl - - # Disable gomnd for main.go as it often contains setup constants - - path: "main\\.go" - linters: - - gomnd - - # Disable some linters for cmd package (CLI commands often have many constants) - - path: "cmd/" - linters: - - gomnd - - gochecknoglobals - - max-same-issues: 0 max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/cmd/config.go b/cmd/config.go index fed3ee0..37b8c41 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -15,6 +15,7 @@ func getConfigDir() string { if err != nil { return os.TempDir() } + return filepath.Join(homeDir, ".sim-cli") } @@ -25,7 +26,7 @@ func getConfigPath() string { func loadConfig() (*Config, error) { configPath := getConfigPath() - if err := os.MkdirAll(getConfigDir(), 0755); err != nil { + if err := os.MkdirAll(getConfigDir(), 0o755); err != nil { return &Config{}, err } @@ -49,7 +50,7 @@ func loadConfig() (*Config, error) { func saveConfig(config *Config) error { configPath := getConfigPath() - if err := os.MkdirAll(getConfigDir(), 0755); err != nil { + if err := os.MkdirAll(getConfigDir(), 0o755); err != nil { return err } @@ -58,7 +59,7 @@ func saveConfig(config *Config) error { return err } - return os.WriteFile(configPath, data, 0644) + return os.WriteFile(configPath, data, 0o600) } func saveLastStartedDevice(device *Device) error { @@ -68,6 +69,7 @@ func saveLastStartedDevice(device *Device) error { } config.LastStartedDevice = device + return saveConfig(config) } diff --git a/cmd/device.go b/cmd/device.go index a61f0f5..aa4a9cd 100644 --- a/cmd/device.go +++ b/cmd/device.go @@ -9,6 +9,11 @@ import ( "github.com/spf13/cobra" ) +const ( + darwinOS = "darwin" + bootedState = "Booted" +) + var startCmd = &cobra.Command{ Use: "start [device-name-or-udid|lts]", Aliases: []string{"s"}, @@ -31,7 +36,7 @@ Use 'lts' to start the last started device.`, deviceID = lastDevice.Name } - if runtime.GOOS == "darwin" { + if runtime.GOOS == darwinOS { if startIOSSimulator(deviceID) { return } @@ -54,7 +59,7 @@ var stopCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { deviceID := args[0] - if runtime.GOOS == "darwin" { + if runtime.GOOS == darwinOS { if stopIOSSimulator(deviceID) { return } @@ -118,8 +123,9 @@ var deleteCmd = &cobra.Command{ Use: "delete [device-name-or-udid]", Aliases: []string{"d", "del"}, Short: "Delete an iOS simulator or Android emulator", - Long: `Delete a specific iOS simulator or Android emulator by name or UDID. This will permanently remove the device.`, - Args: cobra.ExactArgs(1), + Long: `Delete a specific iOS simulator or Android emulator by name or UDID. ` + + `This will permanently remove the device.`, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { deviceID := args[0] @@ -216,6 +222,7 @@ func startIOSSimulator(deviceID string) bool { } fmt.Printf("iOS simulator '%s' started successfully\n", deviceID) + return true } @@ -233,6 +240,7 @@ func stopIOSSimulator(deviceID string) bool { } fmt.Printf("iOS simulator '%s' stopped successfully\n", deviceID) + return true } @@ -249,7 +257,7 @@ func restartIOSSimulator(deviceID string) bool { fmt.Printf("Restarting iOS simulator '%s'...\n", deviceID) shutdownCmd := exec.Command("xcrun", "simctl", "shutdown", device.UDID) - shutdownCmd.Run() // Ignore error if already shutdown + _ = shutdownCmd.Run() // Ignore error if already shutdown bootCmd := exec.Command("xcrun", "simctl", "boot", device.UDID) if err := bootCmd.Run(); err != nil { @@ -269,6 +277,7 @@ func restartIOSSimulator(deviceID string) bool { } fmt.Printf("iOS simulator '%s' restarted successfully\n", deviceID) + return true } @@ -313,6 +322,7 @@ func startAndroidEmulator(deviceID string) bool { } fmt.Printf("Android emulator '%s' started successfully\n", deviceID) + return true } @@ -330,6 +340,7 @@ func stopAndroidEmulator(deviceID string) bool { } fmt.Printf("Android emulator '%s' stopped successfully\n", deviceID) + return true } @@ -349,6 +360,7 @@ func restartAndroidEmulator(deviceID string) bool { if err := saveLastStartedDevice(device); err != nil { fmt.Printf("Warning: Could not save last started device: %v\n", err) } + return true } @@ -365,7 +377,7 @@ func deleteIOSSimulator(deviceID string) bool { // Shutdown the simulator if it's running shutdownCmd := exec.Command("xcrun", "simctl", "shutdown", udid) - shutdownCmd.Run() // Ignore error if already shutdown + _ = shutdownCmd.Run() // Ignore error if already shutdown // Delete the simulator cmd := exec.Command("xcrun", "simctl", "delete", udid) @@ -375,6 +387,7 @@ func deleteIOSSimulator(deviceID string) bool { } fmt.Printf("iOS simulator '%s' deleted successfully\n", deviceID) + return true } @@ -396,6 +409,7 @@ func deleteAndroidEmulator(deviceID string) bool { } fmt.Printf("Android emulator '%s' deleted successfully\n", deviceID) + return true } @@ -421,6 +435,7 @@ func findIOSSimulatorByID(deviceID string) *Device { return &sim } } + return nil } diff --git a/cmd/list.go b/cmd/list.go index fd3cfb8..ccb3500 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -60,10 +60,10 @@ var listCmd = &cobra.Command{ } } - table.Append(device.Type, device.Name, device.State, udid, runtime) + _ = table.Append(device.Type, device.Name, device.State, udid, runtime) } - table.Render() + _ = table.Render() }, } diff --git a/cmd/media.go b/cmd/media.go index b74dcd2..86e8e36 100644 --- a/cmd/media.go +++ b/cmd/media.go @@ -96,6 +96,7 @@ func takeIOSScreenshot(deviceID, outputFile string) bool { } fmt.Printf("Screenshot saved to: %s\n", outputFile) + return true } @@ -125,9 +126,10 @@ func takeAndroidScreenshot(deviceID, outputFile string) bool { } cleanupCmd := exec.Command("adb", "-s", runningUDID, "shell", "rm", devicePath) - cleanupCmd.Run() // Ignore errors + _ = cleanupCmd.Run() // Ignore errors fmt.Printf("Screenshot saved to: %s\n", outputFile) + return true } @@ -165,7 +167,7 @@ func recordIOSScreen(deviceID, outputFile string, duration int) bool { fmt.Printf("Error stopping recording: %v\n", err) } - cmd.Wait() + _ = cmd.Wait() } else { fmt.Println("Press Ctrl+C to stop recording...") if err := cmd.Run(); err != nil { @@ -175,6 +177,7 @@ func recordIOSScreen(deviceID, outputFile string, duration int) bool { } fmt.Printf("Recording saved to: %s\n", outputFile) + return true } @@ -214,9 +217,10 @@ func recordAndroidScreen(deviceID, outputFile string, duration int) bool { } cleanupCmd := exec.Command("adb", "-s", runningUDID, "shell", "rm", devicePath) - cleanupCmd.Run() // Ignore errors + _ = cleanupCmd.Run() // Ignore errors fmt.Printf("Recording saved to: %s\n", outputFile) + return true } diff --git a/tests/config_test.go b/tests/config_test.go index 0c96efd..b14cd8d 100644 --- a/tests/config_test.go +++ b/tests/config_test.go @@ -109,11 +109,11 @@ func TestLoadConfig_CorruptedFile(t *testing.T) { // Create corrupted config file configDir := filepath.Join(tempDir, ".sim-cli") - os.MkdirAll(configDir, 0755) + _ = os.MkdirAll(configDir, 0o755) configPath := filepath.Join(configDir, "config.json") // Write invalid JSON - os.WriteFile(configPath, []byte("invalid json {"), 0644) + _ = os.WriteFile(configPath, []byte("invalid json {"), 0o644) config, err := loadConfig() if err == nil { @@ -126,12 +126,13 @@ func TestLoadConfig_CorruptedFile(t *testing.T) { } } -// Helper functions that mirror the unexported functions in cmd package +// Helper functions that mirror the unexported functions in cmd package. func getConfigDir() string { homeDir, err := os.UserHomeDir() if err != nil { return os.TempDir() } + return filepath.Join(homeDir, ".sim-cli") } @@ -142,7 +143,7 @@ func getConfigPath() string { func loadConfig() (*Config, error) { configPath := getConfigPath() - if err := os.MkdirAll(getConfigDir(), 0755); err != nil { + if err := os.MkdirAll(getConfigDir(), 0o755); err != nil { return &Config{}, err } @@ -166,7 +167,7 @@ func loadConfig() (*Config, error) { func saveConfig(config *Config) error { configPath := getConfigPath() - if err := os.MkdirAll(getConfigDir(), 0755); err != nil { + if err := os.MkdirAll(getConfigDir(), 0o755); err != nil { return err } @@ -175,7 +176,7 @@ func saveConfig(config *Config) error { return err } - return os.WriteFile(configPath, data, 0644) + return os.WriteFile(configPath, data, 0o644) } func saveLastStartedDevice(device *Device) error { @@ -185,5 +186,6 @@ func saveLastStartedDevice(device *Device) error { } config.LastStartedDevice = device + return saveConfig(config) } diff --git a/tests/device_test.go b/tests/device_test.go index 5ad96e1..966fb14 100644 --- a/tests/device_test.go +++ b/tests/device_test.go @@ -232,6 +232,7 @@ func findIOSSimulatorByID(deviceID string) *Device { return &sim } } + return nil } @@ -242,6 +243,7 @@ func doesAndroidAVDExist(avdName string) bool { return true } } + return false } @@ -256,6 +258,7 @@ func findRunningAndroidEmulator(avdName string) string { return emu.UDID } } + return "" } @@ -264,10 +267,11 @@ func getLastStartedDevice() (*Device, error) { if err != nil { return nil, err } + return config.LastStartedDevice, nil } -// Mock implementations for testing device operations +// Mock implementations for testing device operations. func stopIOSSimulator(deviceID string) bool { // In a real implementation, this would call xcrun simctl shutdown // For testing, we just check if the device exists diff --git a/tests/integration_test.go b/tests/integration_test.go index 3cd29d9..ecbf628 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -228,7 +228,7 @@ func TestIntegration_ErrorHandling(t *testing.T) { // Test 3: Corrupted config recovery // Write invalid JSON to config file configPath := getConfigPath() - err = os.WriteFile(configPath, []byte("invalid json"), 0644) + err = os.WriteFile(configPath, []byte("invalid json"), 0o644) if err != nil { t.Fatalf("Failed to write invalid config: %v", err) } @@ -457,7 +457,7 @@ func TestSecurity_ConfigFilePermissions(t *testing.T) { } mode := info.Mode() - if mode.Perm() != 0644 { + if mode.Perm() != 0o644 { t.Errorf("Expected config file permissions 0644, got %o", mode.Perm()) } } @@ -490,7 +490,7 @@ func TestSecurity_ConfigDirectory(t *testing.T) { } mode := info.Mode() - if mode.Perm() != 0755 { + if mode.Perm() != 0o755 { t.Errorf("Expected config directory permissions 0755, got %o", mode.Perm()) } } diff --git a/tests/list_test.go b/tests/list_test.go index 8bbe6d0..8798960 100644 --- a/tests/list_test.go +++ b/tests/list_test.go @@ -125,7 +125,7 @@ func TestListCommand_NoDevices(t *testing.T) { } } -// Helper functions that mirror the unexported functions in cmd package +// Helper functions that mirror the unexported functions in cmd package. func getIOSSimulators() []Device { cmd := exec.Command("xcrun", "simctl", "list", "devices", "--json") output, err := cmd.Output() diff --git a/tests/media_test.go b/tests/media_test.go index 3556ae2..e854281 100644 --- a/tests/media_test.go +++ b/tests/media_test.go @@ -226,7 +226,7 @@ func TestRecordCommand_DurationFlag(t *testing.T) { // Helper functions that mirror the logic in cmd package for testing -func takeIOSScreenshot(deviceID, outputFile string) bool { +func takeIOSScreenshot(deviceID, _ string) bool { udid := findIOSSimulatorUDID(deviceID) if udid == "" { return false @@ -235,7 +235,7 @@ func takeIOSScreenshot(deviceID, outputFile string) bool { return false // Simulate command execution failure for testing } -func takeAndroidScreenshot(deviceID, outputFile string) bool { +func takeAndroidScreenshot(deviceID, _ string) bool { runningUDID := findRunningAndroidEmulator(deviceID) if runningUDID == "" { return false @@ -244,7 +244,7 @@ func takeAndroidScreenshot(deviceID, outputFile string) bool { return false // Simulate command execution failure for testing } -func recordIOSScreen(deviceID, outputFile string, duration int) bool { +func recordIOSScreen(deviceID, _ string, duration int) bool { udid := findIOSSimulatorUDID(deviceID) if udid == "" { return false @@ -253,7 +253,7 @@ func recordIOSScreen(deviceID, outputFile string, duration int) bool { return false // Simulate command execution failure for testing } -func recordAndroidScreen(deviceID, outputFile string, duration int) bool { +func recordAndroidScreen(deviceID, _ string, duration int) bool { runningUDID := findRunningAndroidEmulator(deviceID) if runningUDID == "" { return false @@ -276,6 +276,7 @@ func ensurePNGExtension(outputFile string) string { if !strings.HasSuffix(strings.ToLower(outputFile), ".png") { outputFile = strings.TrimSuffix(outputFile, filepath.Ext(outputFile)) + ".png" } + return strings.ToLower(outputFile) } @@ -283,6 +284,7 @@ func ensureMP4Extension(outputFile string) string { if !strings.HasSuffix(strings.ToLower(outputFile), ".mp4") { outputFile = strings.TrimSuffix(outputFile, filepath.Ext(outputFile)) + ".mp4" } + return strings.ToLower(outputFile) } diff --git a/tests/test_utils.go b/tests/test_utils.go index cc8c845..aff82d1 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -5,12 +5,12 @@ import ( "testing" ) -// Config struct mirrors the one in cmd package for testing +// Config struct mirrors the one in cmd package for testing. type Config struct { LastStartedDevice *Device `json:"lastStartedDevice,omitempty"` } -// Device struct mirrors the one in cmd package for testing +// Device struct mirrors the one in cmd package for testing. type Device struct { Name string `json:"name"` UDID string `json:"udid"` @@ -20,13 +20,13 @@ type Device struct { DeviceType string `json:"deviceTypeIdentifier,omitempty"` } -// TestHelpers provides utility functions for testing +// TestHelpers provides utility functions for testing. type TestHelpers struct { tempDir string originalHome string } -// NewTestHelpers creates a new test helpers instance +// NewTestHelpers creates a new test helpers instance. func NewTestHelpers(t *testing.T) *TestHelpers { tempDir := t.TempDir() originalHome := os.Getenv("HOME") @@ -37,17 +37,17 @@ func NewTestHelpers(t *testing.T) *TestHelpers { } } -// SetupTempHome sets up a temporary home directory for testing +// SetupTempHome sets up a temporary home directory for testing. func (h *TestHelpers) SetupTempHome() { os.Setenv("HOME", h.tempDir) } -// RestoreHome restores the original home directory +// RestoreHome restores the original home directory. func (h *TestHelpers) RestoreHome() { os.Setenv("HOME", h.originalHome) } -// CreateTestDevice creates a test device with default values +// CreateTestDevice creates a test device with default values. func CreateTestDevice(name string) *Device { return &Device{ Name: name, @@ -58,7 +58,7 @@ func CreateTestDevice(name string) *Device { } } -// CreateTestConfig creates a test config with a test device +// CreateTestConfig creates a test config with a test device. func CreateTestConfig() *Config { return &Config{ LastStartedDevice: CreateTestDevice("test-device"), @@ -66,14 +66,14 @@ func CreateTestConfig() *Config { } // MockExecCommand can be used to mock exec.Command calls in tests -// This would be expanded with actual mocking functionality +// This would be expanded with actual mocking functionality. type MockExecCommand struct { Commands []string Outputs map[string]string Errors map[string]error } -// NewMockExecCommand creates a new mock exec command +// NewMockExecCommand creates a new mock exec command. func NewMockExecCommand() *MockExecCommand { return &MockExecCommand{ Commands: []string{}, @@ -82,17 +82,17 @@ func NewMockExecCommand() *MockExecCommand { } } -// SetOutput sets the expected output for a command +// SetOutput sets the expected output for a command. func (m *MockExecCommand) SetOutput(command, output string) { m.Outputs[command] = output } -// SetError sets the expected error for a command +// SetError sets the expected error for a command. func (m *MockExecCommand) SetError(command string, err error) { m.Errors[command] = err } -// AssertCommandCalled checks if a command was called +// AssertCommandCalled checks if a command was called. func (m *MockExecCommand) AssertCommandCalled(t *testing.T, command string) { for _, cmd := range m.Commands { if cmd == command { @@ -102,46 +102,53 @@ func (m *MockExecCommand) AssertCommandCalled(t *testing.T, command string) { t.Errorf("Expected command %s to be called", command) } -// TestDeviceData provides common test device data -var TestDeviceData = struct { +// GetTestDeviceData provides common test device data. +func GetTestDeviceData() struct { IOSSimulator Device AndroidEmulator Device BootedSimulator Device ShutdownEmulator Device -}{ - IOSSimulator: Device{ - Name: "iPhone 15", - UDID: "12345678-1234-5678-9012-123456789012", - State: "Shutdown", - Type: "iOS Simulator", - Runtime: "iOS 17.0", - DeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-15", - }, - AndroidEmulator: Device{ - Name: "Pixel_7_API_34", - UDID: "emulator-5554", - State: "Shutdown", - Type: "Android Emulator", - Runtime: "Android", - }, - BootedSimulator: Device{ - Name: "iPhone 15 Pro", - UDID: "87654321-4321-8765-2109-876543210987", - State: "Booted", - Type: "iOS Simulator", - Runtime: "iOS 17.0", - DeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro", - }, - ShutdownEmulator: Device{ - Name: "Pixel_8_API_34", - UDID: "offline", - State: "Shutdown", - Type: "Android Emulator", - Runtime: "Android", - }, -} - -// ValidateDevice validates that a device has required fields +} { + return struct { + IOSSimulator Device + AndroidEmulator Device + BootedSimulator Device + ShutdownEmulator Device + }{ + IOSSimulator: Device{ + Name: "iPhone 15", + UDID: "12345678-1234-5678-9012-123456789012", + State: "Shutdown", + Type: "iOS Simulator", + Runtime: "iOS 17.0", + DeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-15", + }, + AndroidEmulator: Device{ + Name: "Pixel_7_API_34", + UDID: "emulator-5554", + State: "Shutdown", + Type: "Android Emulator", + Runtime: "Android", + }, + BootedSimulator: Device{ + Name: "iPhone 15 Pro", + UDID: "87654321-4321-8765-2109-876543210987", + State: "Booted", + Type: "iOS Simulator", + Runtime: "iOS 17.0", + DeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro", + }, + ShutdownEmulator: Device{ + Name: "Pixel_8_API_34", + UDID: "offline", + State: "Shutdown", + Type: "Android Emulator", + Runtime: "Android", + }, + } +} + +// ValidateDevice validates that a device has required fields. func ValidateDevice(t *testing.T, device *Device) { if device == nil { t.Fatal("Device should not be nil") @@ -186,7 +193,7 @@ func ValidateDevice(t *testing.T, device *Device) { } } -// ValidateConfig validates that a config is properly structured +// ValidateConfig validates that a config is properly structured. func ValidateConfig(t *testing.T, config *Config) { if config == nil { t.Fatal("Config should not be nil") @@ -197,7 +204,7 @@ func ValidateConfig(t *testing.T, config *Config) { } } -// AssertDeviceEqual asserts that two devices are equal +// AssertDeviceEqual asserts that two devices are equal. func AssertDeviceEqual(t *testing.T, expected, actual *Device) { if expected == nil && actual == nil { return From 00e077b5ff2be231281320a07e4da60e2d995c0e Mon Sep 17 00:00:00 2001 From: Annurdien Rasyid Date: Tue, 22 Jul 2025 14:35:29 +0700 Subject: [PATCH 4/6] feat: update golangci-lint action to version 8.0.0 in CI workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 461cbc7..6143365 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: run: go mod download - name: Run golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v8.0.0 with: version: latest args: --timeout=5m From 0c56d9b497a7ff805bbfd3b9b89e0565ec9eb68b Mon Sep 17 00:00:00 2001 From: Annurdien Rasyid Date: Tue, 22 Jul 2025 14:42:47 +0700 Subject: [PATCH 5/6] feat: remove 'noctx' linter from golangci-lint configuration --- .golangci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 9b80c59..33bbddd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,7 +35,6 @@ linters: - nestif - nilerr - nlreturn - - noctx - nolintlint - prealloc - predeclared From dad4a1331322211cd55159b50aad104c6058bb6b Mon Sep 17 00:00:00 2001 From: Annurdien Rasyid Date: Tue, 22 Jul 2025 14:45:21 +0700 Subject: [PATCH 6/6] feat: rename CI pipeline to 'Test and Build' and update job name to 'Integration Tests' --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6143365..f4ef83b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI Pipeline +name: Test and Build on: push: @@ -8,7 +8,7 @@ on: jobs: ci: - name: CI + name: Integration Tests runs-on: ubuntu-latest steps: