name: Eino Tests on: pull_request: push: branches: - main env: DEFAULT_GO_VERSION: "1.18" jobs: unit-test: name: eino-unit-test runs-on: ubuntu-latest permissions: contents: write pull-requests: write repository-projects: write env: COVERAGE_FILE: coverage.out BREAKDOWN_FILE: main.breakdown steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ env.DEFAULT_GO_VERSION }} - name: Exec Go Test run: | modules=`find . -name "go.mod" -exec dirname {} \;` echo $modules list="" coverpkg="" if [[ ! -f "go.work" ]];then go work init;fi for module in $modules; do go work use $module; list=$module"/... "$list; coverpkg=$module"/...,"$coverpkg; done go work sync go test -race -v -coverprofile=${{ env.COVERAGE_FILE }} -gcflags="all=-l -N" -coverpkg=$coverpkg $list - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: name: eino-unit-test env_vars: GOLANG,EINO files: ${{ env.COVERAGE_FILE }} token: ${{ secrets.CODECOV_TOKEN }} codecov_yml_path: ./github/.codecov.yml benchmark-test: runs-on: ubuntu-latest permissions: contents: write pull-requests: write repository-projects: write steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ env.DEFAULT_GO_VERSION }} - name: Run Benchmark Tests run: go test -bench=. -benchmem -run=none ./... compatibility-test: strategy: matrix: go: [ "1.19", "1.20", "1.21", "1.22", "1.23", "1.24" ] runs-on: ubuntu-latest permissions: contents: write pull-requests: write repository-projects: write steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} cache: true - name: Compatibility Test run: | # just basic unit test, no coverage report go test -race ./... api-compatibility: name: api-compatibility-check runs-on: ubuntu-latest permissions: contents: write pull-requests: write repository-projects: write if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.22" - name: Install go-apidiff run: go install github.com/joelanford/go-apidiff@v0.8.2 - name: Check API compatibility id: apidiff run: | BASE_SHA=${{ github.event.pull_request.base.sha }} HEAD_SHA=${{ github.event.pull_request.head.sha }} echo "Checking API compatibility between $BASE_SHA and $HEAD_SHA" go mod tidy if ! DIFF_OUTPUT=$(go-apidiff $BASE_SHA $HEAD_SHA 2>&1); then echo "go-apidiff output: $DIFF_OUTPUT" fi echo "diff_output<> $GITHUB_ENV echo "$DIFF_OUTPUT" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV if echo "$DIFF_OUTPUT" | grep -q "Incompatible changes:"; then echo "has_breaking_changes=true" >> $GITHUB_OUTPUT else echo "has_breaking_changes=false" >> $GITHUB_OUTPUT fi - name: Create Review Thread if: steps.apidiff.outputs.has_breaking_changes == 'true' continue-on-error: true uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const reviewComments = await github.rest.pulls.listReviewComments({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); const existingPackageComments = new Map(); for (const comment of reviewComments.data) { if (comment.body.includes('Breaking API Changes Detected')) { const packageMatch = comment.body.match(/Package: `([^`]+)`/); if (packageMatch) { const pkg = packageMatch[1]; if (!existingPackageComments.has(pkg)) { existingPackageComments.set(pkg, new Set()); } existingPackageComments.get(pkg).add(comment.path); } } } const files = await github.rest.pulls.listFiles({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); const diffOutput = process.env.diff_output || ''; const breakingChanges = new Map(); let currentPackage = ''; let isInIncompatibleSection = false; const lines = diffOutput.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('github.com/')) { currentPackage = line; if (!breakingChanges.has(currentPackage)) { breakingChanges.set(currentPackage, []); } continue; } if (line === 'Incompatible changes:') { isInIncompatibleSection = true; continue; } if (line === '') { isInIncompatibleSection = false; continue; } if (isInIncompatibleSection && line.startsWith('- ')) { const change = line.substring(2); if (currentPackage) { breakingChanges.get(currentPackage).push(change); } } } const changedFiles = files.data; for (const [pkg, changes] of breakingChanges) { if (changes.length === 0) continue; const pkgPath = pkg.split('/').slice(3).join('/'); const matchingFile = changedFiles.find(file => file.filename.includes(pkgPath) ) || changedFiles[0]; const hasCommentForPackage = existingPackageComments.has(pkg) && existingPackageComments.get(pkg).has(matchingFile.filename); if (matchingFile && !hasCommentForPackage) { const changesList = changes.map(change => { const [name, desc] = change.split(':').map(s => s.trim()); return `- **${name}:** ${desc}`; }).join('\n'); const commentBody = [ '🚨 **Breaking API Changes Detected**', '', `Package: \`${pkg}\``, '', 'Incompatible changes:', changesList, '', '
', 'Review Guidelines', '', 'Please ensure that:', '- The changes are absolutely necessary', '- They are properly documented', '- Migration guides are provided if needed', '
', '', '⚠️ Please resolve this thread after reviewing the breaking changes.' ].join('\n'); await github.rest.pulls.createReview({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, event: 'COMMENT', comments: [{ path: matchingFile.filename, position: matchingFile.patch ? matchingFile.patch.split('\n').findIndex(line => line.startsWith('+')) + 1 : 1, body: commentBody }] }); if (!existingPackageComments.has(pkg)) { existingPackageComments.set(pkg, new Set()); } existingPackageComments.get(pkg).add(matchingFile.filename); } }