Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"build:spec-test-data": "node \"test/spec-tests/generate-cql.js\" && cd \"./test/generator\" && \"./gradlew\" generateSpecTestData && cd ../.. && node \"test/spec-tests/prettify-json.js\"",
"build:all": "npm run build && npm run build:browserify && npm run build:test-data && npm run build:spec-test-data",
"watch:test-data": "cd \"./test/generator\" && \"./gradlew\" watchTestData && cd ..",
"check-skip-list": "node \"test/spec-tests/check-skip-list.js\"",
"test": "cross-env TS_NODE_PROJECT=\"./test/tsconfig.json\" TS_NODE_FILES=true mocha --reporter spec --recursive",
"test:nyc": "cross-env TS_NODE_PROJECT=\"./test/tsconfig.json\" TS_NODE_FILES=true nyc --reporter=html npx mocha --reporter spec --recursive",
"test:watch": "cross-env TS_NODE_PROJECT=\"./test/tsconfig.json\" TS_NODE_FILES=true mocha --reporter spec --recursive --watch",
Expand Down
183 changes: 183 additions & 0 deletions test/spec-tests/check-skip-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Iteratively recheck each skip-list entry by temporarily commenting it out,
// rebuilding, and running tests. Keeps entries that still cause failures,
// removes entries that now pass. Preserves all other lines as-is.

/* eslint-disable no-console */

const fs = require('fs/promises');
const path = require('path');
const { spawn } = require('child_process');

const SKIP_LIST_PATH = path.join(process.cwd(), 'test', 'spec-tests', 'skip-list.txt');

function run(cmd, args, opts = {}) {
return new Promise(resolve => {
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], ...opts });
let stdout = '';
let stderr = '';
child.stdout.on('data', d => (stdout += d.toString()));
child.stderr.on('data', d => (stderr += d.toString()));
child.on('close', code => resolve({ code, stdout, stderr }));
});
}

async function rebuildSpec() {
const r = await run('npm', ['run', 'build:spec-test-data']);
if (r.code !== 0) {
const err = new Error('Spec test data build failed');
err.detail = r.stderr || r.stdout;
throw err;
}
}

async function runTests() {
const r = await run('npm', ['test', '--', 'test/spec-tests']);
if (r.code !== 0) {
return { ok: false };
}
return { ok: true };
}

function isComment(line) {
return line.trimStart().startsWith('#');
}
function isBlank(line) {
return line.trim() === '';
}
function firstToken(line) {
const m = line.match(/^\s*("[^"]+"|\S+)/);
return m ? m[1] : '';
}

async function main() {
// Ensure the script is run from the project root
try {
await fs.stat(SKIP_LIST_PATH);
} catch {
console.error(
'The check-skip-list script must be run from the project root directory. Use "npm run check-skip-list".'
);
process.exit(1);
}

console.log('NOTE: This script incrementally modifies the file: test/spec-tests/skip-list.txt.');
console.log(
'If you abort the script before it is completed, check that file to ensure it is correct.\n'
);

const originalText = await fs.readFile(SKIP_LIST_PATH, 'utf8');
const nl = originalText.includes('\r\n') ? '\r\n' : '\n';
let lines = originalText.split(/\r?\n/);

// Baseline: ensure tests pass before any changes
console.log('Running baseline tests...');
const base = await runTests();
if (!base.ok) {
console.error('Baseline tests failed. Aborting without changes.');
process.exit(1);
}
console.log('Baseline tests passed.');

let totalCandidateTests = 0;
let totalCandidateSuites = 0;
let processed = 0;
let removed = 0;
let kept = 0;

// First count the total candidates
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (isBlank(line) || isComment(line)) {
continue;
}
const parts = firstToken(line).split('.');
if (parts.length > 2) {
totalCandidateTests++;
} else {
totalCandidateSuites++;
}
}
console.log(
`\nOriginal skip-list.txt file skips ${totalCandidateSuites} test suites and ${totalCandidateTests} test cases.`
);

// Iterate through skip-list entries
for (let i = 0; i < lines.length; ) {
const line = lines[i];
if (isBlank(line) || isComment(line)) {
i++;
continue;
}
processed++;

const token = firstToken(line);
const originalLine = line;
const commented = '# ' + originalLine;

// Comment out current line
lines[i] = commented;
await fs.writeFile(SKIP_LIST_PATH, lines.join(nl), 'utf8');

// Rebuild then tests
console.log(
`\n[${processed}/${totalCandidateSuites + totalCandidateTests}] Testing without skipping ${token}`
);
try {
await rebuildSpec();
} catch (e) {
console.error('Build failed after commenting out a line. Restoring and continuing.');
console.error(e.detail || e.message);
lines[i] = originalLine;
await fs.writeFile(SKIP_LIST_PATH, lines.join(nl), 'utf8');
i++; // move to next line
continue;
}

const res = await runTests();
if (!res.ok) {
// Test run failed -> this entry still needs to be skipped; restore line
console.log(`- Still failing. Keeping skip.`);
lines[i] = originalLine;
await fs.writeFile(SKIP_LIST_PATH, lines.join(nl), 'utf8');
kept++;
i++; // move to next line
} else {
// Test run passed -> remove this line permanently
console.log(`- Now passing. Removing skip.`);
lines.splice(i, 1);
await fs.writeFile(SKIP_LIST_PATH, lines.join(nl), 'utf8');
removed++;
// do not increment i; next line shifts into this index
}
}

// Final rebuild + verification run
console.log('\nRebuilding spec test data for final verification...');
try {
await rebuildSpec();
} catch (e) {
console.error('Final build failed. Aborting with current state preserved.');
console.error(e.detail || e.message);
process.exit(1);
}

console.log('Running final tests...');
const finalRes = await runTests();
if (!finalRes.ok) {
console.error(
'Final tests failed after processing skip-list. Aborting with current state preserved.'
);
process.exit(1);
}

console.log('\nDone.');
console.log(`- Total candidate entries checked: ${processed}`);
console.log(`- Removed (now passing): ${removed}`);
console.log(`- Kept (still failing): ${kept}`);
process.exit(0);
}

main().catch(err => {
console.error('Unexpected error:', err.message);
process.exit(1);
});
44 changes: 24 additions & 20 deletions test/spec-tests/cql/CqlDateTimeOperatorsTest.cql
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ define "Add": Tuple{
output: true
},
"DateTimeAddYearInWeeks": Tuple{
skipped: 'Wrong output: 2018-05-23 + 52 weeks should be 2019-05-22 (both dates should be Wednesdays)'
/*
expression: DateTime(2018, 5, 23) + 52 weeks = DateTime(2019, 5, 23),
expression: DateTime(2018, 5, 23) + 52 weeks = DateTime(2019, 5, 22),
output: true
*/ },
},
"DateTimeLeapDayAddYearInWeeks": Tuple{
expression: DateTime(2023, 3, 2) + 52 weeks = DateTime(2024, 2, 29),
output: true
},
"DateTimeLeapYearAddYearInWeeks": Tuple{
expression: DateTime(2024, 2, 28) + 52 weeks = DateTime(2025, 2, 26),
output: true
},
"DateTimeAdd5Days": Tuple{
expression: DateTime(2005, 5, 10) + 5 days,
output: @2005-05-15T
Expand Down Expand Up @@ -418,11 +424,9 @@ define "DateTimeComponentFrom": Tuple{
output: 955
},
"DateTimeComponentFromTimezone": Tuple{
skipped: 'Translation Error: Timezone keyword is only valid in 1.3 or lower'
/*
expression: timezone from DateTime(2003, 10, 29, 20, 50, 33, 955, 1),
invalid: true
*/ },
},
"DateTimeComponentFromTimezone2": Tuple{
expression: timezoneoffset from DateTime(2003, 10, 29, 20, 50, 33, 955, 1),
output: 1.00
Expand Down Expand Up @@ -696,17 +700,13 @@ define "Uncertainty tests": Tuple{
output: 2
},
"TimeDurationBetweenHourDiffPrecision": Tuple{
skipped: 'Translation Error: Syntax error at Z'
/*
expression: hours between @T06Z and @T07:00:00Z,
invalid: true
*/ },
},
"TimeDurationBetweenHourDiffPrecision2": Tuple{
skipped: 'Wrong output: Duration in hours between T06 and T07:00:00 should be Uncertainty[0, 1]'
/*
expression: hours between @T06 and @T07:00:00,
output: 1
*/ },
},
"TimeDurationBetweenMinute": Tuple{
expression: minutes between @T23:20:16.555 and @T23:25:15.555,
output: 4
Expand Down Expand Up @@ -1179,11 +1179,17 @@ define "Subtract": Tuple{
output: true
},
"DateTimeSubtractYearInWeeks": Tuple{
skipped: 'Wrong output: 2018-05-23 - 52 weeks should be 2019-05-24 (both dates should be Wednesdays)'
/*
expression: DateTime(2018, 5, 23) - 52 weeks = DateTime(2017, 5, 23),
expression: DateTime(2018, 5, 23) - 52 weeks = DateTime(2017, 5, 24),
output: true
},
"DateTimeLeapDaySubtractYearInWeeks": Tuple{
expression: DateTime(2024, 2, 29) - 52 weeks = DateTime(2023, 3, 2),
output: true
*/ },
},
"DateTimeLeapYearSubtractYearInWeeks": Tuple{
expression: DateTime(2024, 3, 1) - 52 weeks = DateTime(2023, 3, 3),
output: true
},
"DateTimeSubtract5Days": Tuple{
expression: DateTime(2005, 5, 10) - 5 days,
output: @2005-05-05T
Expand Down Expand Up @@ -1213,11 +1219,9 @@ define "Subtract": Tuple{
output: @2005-05-10T05:05:05
},
"DateTimeSubtract1YearInSeconds": Tuple{
skipped: 'Spec says to convert more precise duration to most precise unit in Date. How do you convert seconds to months? 31535999 is 364.999 days, which isn\'t quite 12 months, so we answer 2015-06.'
/*
expression: DateTime(2016,5) - 31535999 seconds = DateTime(2015, 5),
output: true
*/ },
},
"DateTimeSubtract15HourPrecisionSecond": Tuple{
expression: DateTime(2016, 10, 1, 10, 20, 30) - 15 hours,
output: @2016-09-30T19:20:30
Expand Down
Loading
Loading