From 70eb0f68aeecb306b8fed94ff5960deb97435b7c Mon Sep 17 00:00:00 2001 From: CaoBochun Date: Wed, 5 Aug 2026 15:32:11 +0800 Subject: [PATCH] fix: make GAN harness score parsing portable --- scripts/gan-harness.sh | 23 ++++++++---- tests/gan-harness.test.js | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 tests/gan-harness.test.js diff --git a/scripts/gan-harness.sh b/scripts/gan-harness.sh index 9aa4289ca..093e696d1 100755 --- a/scripts/gan-harness.sh +++ b/scripts/gan-harness.sh @@ -61,11 +61,18 @@ phase() { echo -e "\n${PURPLE}════════════════ extract_score() { # Extract the TOTAL weighted score from a feedback file local file="$1" - # Look for **TOTAL** or **X.X/10** pattern - grep -oP '(?<=\*\*TOTAL\*\*.*\*\*)[0-9]+\.[0-9]+' "$file" 2>/dev/null \ - || grep -oP '(?<=TOTAL.*\|.*\| \*\*)[0-9]+\.[0-9]+' "$file" 2>/dev/null \ - || grep -oP 'Verdict:.*([0-9]+\.[0-9]+)' "$file" 2>/dev/null | grep -oP '[0-9]+\.[0-9]+' \ - || echo "0.0" + awk ' + /\*\*TOTAL\*\*/ || /Verdict:/ { + if (match($0, /[0-9]+[.][0-9]+/)) { + print substr($0, RSTART, RLENGTH) + found = 1 + exit + } + } + END { + if (!found) print "0.0" + } + ' "$file" 2>/dev/null } score_passes() { @@ -241,8 +248,12 @@ done phase "PHASE 3: Build Report" -FINAL_SCORE="${SCORES[-1]:-0.0}" NUM_ITERATIONS=${#SCORES[@]} +if [ "$NUM_ITERATIONS" -gt 0 ]; then + FINAL_SCORE="${SCORES[$((NUM_ITERATIONS - 1))]}" +else + FINAL_SCORE="0.0" +fi ELAPSED=$(elapsed) # Build score progression table diff --git a/tests/gan-harness.test.js b/tests/gan-harness.test.js new file mode 100644 index 000000000..74bbcb239 --- /dev/null +++ b/tests/gan-harness.test.js @@ -0,0 +1,74 @@ +/** + * Regression tests for the standalone GAN harness helpers. + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { spawnSync } = require('child_process'); + +const repoRoot = path.resolve(__dirname, '..'); +const harnessPath = path.join(repoRoot, 'scripts', 'gan-harness.sh'); +const harnessSource = fs.readFileSync(harnessPath, 'utf8'); + +let passed = 0; +let failed = 0; + +function test(name, fn) { + try { + fn(); + console.log(` ✓ ${name}`); + passed += 1; + } catch (error) { + console.log(` ✗ ${name}`); + console.log(` Error: ${error.message}`); + failed += 1; + } +} + +function extractScore(feedback) { + const functionMatch = harnessSource.match(/extract_score\(\) \{[\s\S]*?\n\}/); + assert.ok(functionMatch, 'expected scripts/gan-harness.sh to define extract_score'); + + const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-gan-harness-')); + const feedbackPath = path.join(temporaryDirectory, 'feedback.md'); + fs.writeFileSync(feedbackPath, feedback, 'utf8'); + + try { + const result = spawnSync( + '/bin/bash', + ['-c', `${functionMatch[0]}\nextract_score "$1"`, 'gan-harness-score-test', feedbackPath], + { encoding: 'utf8' } + ); + assert.strictEqual(result.status, 0, result.stderr || 'extract_score failed'); + return result.stdout.trim(); + } finally { + fs.rmSync(temporaryDirectory, { recursive: true, force: true }); + } +} + +console.log('\n=== GAN harness helpers ===\n'); + +test('extract_score reads the documented TOTAL table format', () => { + assert.strictEqual(extractScore('| **TOTAL** | | | **7.5** |\n'), '7.5'); +}); + +test('extract_score reads the compact TOTAL format', () => { + assert.strictEqual(extractScore('**TOTAL** | **8.3**\n'), '8.3'); +}); + +test('extract_score reads a Verdict score', () => { + assert.strictEqual(extractScore('Verdict: PASS with score 9.1\n'), '9.1'); +}); + +test('final score lookup is compatible with the macOS Bash 3.2 runtime', () => { + assert.ok(!harnessSource.includes('SCORES[-1]'), 'negative array subscripts require Bash 4.3+'); +}); + +console.log(`\nPassed: ${passed}`); +console.log(`Failed: ${failed}`); + +process.exit(failed > 0 ? 1 : 0);