eyeling 1.28.6 → 1.28.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/browser/eyeling.browser.js +98 -40
- package/eyeling-builtins.ttl +4 -4
- package/eyeling.js +98 -40
- package/lib/builtins.js +98 -40
- package/package.json +1 -1
- package/test/api.test.js +13 -29
- package/test/builtins.test.js +23 -21
- package/test/examples.test.js +27 -42
- package/test/extra.test.js +9 -25
- package/test/manifest.test.js +23 -28
- package/test/package.test.js +17 -24
- package/test/packlist.test.js +5 -14
- package/test/playground.test.js +6 -22
- package/test/rdf12.test.js +15 -54
- package/test/report.js +67 -0
- package/test/stream_messages.test.js +11 -25
package/lib/builtins.js
CHANGED
|
@@ -1168,7 +1168,7 @@ function parseIntegerDatatypeValue(lexical, dt) {
|
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
1170
|
function parseDecimalDatatypeValue(lexical) {
|
|
1171
|
-
const parsed = parseXsdDecimalToBigIntScale(String(lexical));
|
|
1171
|
+
const parsed = parseXsdDecimalToBigIntScale(String(lexical), { trim: false });
|
|
1172
1172
|
if (!parsed) return null;
|
|
1173
1173
|
const canonicalLex = canonicalDecimalLexFromParts(parsed.num, parsed.scale);
|
|
1174
1174
|
return {
|
|
@@ -1294,58 +1294,93 @@ function parseAnyUriDatatypeValue(lexical) {
|
|
|
1294
1294
|
};
|
|
1295
1295
|
}
|
|
1296
1296
|
|
|
1297
|
-
function
|
|
1298
|
-
if (year %
|
|
1299
|
-
if (year %
|
|
1300
|
-
return year %
|
|
1297
|
+
function isLeapYearBigInt(year) {
|
|
1298
|
+
if (year % 400n === 0n) return true;
|
|
1299
|
+
if (year % 100n === 0n) return false;
|
|
1300
|
+
return year % 4n === 0n;
|
|
1301
1301
|
}
|
|
1302
1302
|
|
|
1303
|
-
function
|
|
1304
|
-
if (month === 2) return
|
|
1303
|
+
function daysInMonthBigInt(year, month) {
|
|
1304
|
+
if (month === 2) return isLeapYearBigInt(year) ? 29 : 28;
|
|
1305
1305
|
return [4, 6, 9, 11].includes(month) ? 30 : 31;
|
|
1306
1306
|
}
|
|
1307
1307
|
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1308
|
+
function floorDivBigInt(a, b) {
|
|
1309
|
+
let q = a / b;
|
|
1310
|
+
const r = a % b;
|
|
1311
|
+
if (r !== 0n && ((r > 0n) !== (b > 0n))) q -= 1n;
|
|
1312
|
+
return q;
|
|
1310
1313
|
}
|
|
1311
1314
|
|
|
1312
|
-
function
|
|
1315
|
+
function daysFromCivilBigInt(year, month, day) {
|
|
1313
1316
|
let y = year;
|
|
1314
|
-
|
|
1315
|
-
const era =
|
|
1316
|
-
const yoe = y - era *
|
|
1317
|
-
const mp = month + (month > 2 ? -3 : 9);
|
|
1318
|
-
const doy =
|
|
1319
|
-
const doe = yoe *
|
|
1320
|
-
return era *
|
|
1317
|
+
if (month <= 2) y -= 1n;
|
|
1318
|
+
const era = floorDivBigInt(y, 400n);
|
|
1319
|
+
const yoe = y - era * 400n;
|
|
1320
|
+
const mp = BigInt(month + (month > 2 ? -3 : 9));
|
|
1321
|
+
const doy = (153n * mp + 2n) / 5n + BigInt(day) - 1n;
|
|
1322
|
+
const doe = yoe * 365n + yoe / 4n - yoe / 100n + doy;
|
|
1323
|
+
return era * 146097n + doe - 719468n;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function civilFromDaysBigInt(days) {
|
|
1327
|
+
const z = days + 719468n;
|
|
1328
|
+
const era = floorDivBigInt(z, 146097n);
|
|
1329
|
+
const doe = z - era * 146097n;
|
|
1330
|
+
const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
|
|
1331
|
+
let year = yoe + era * 400n;
|
|
1332
|
+
const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n);
|
|
1333
|
+
const mp = (5n * doy + 2n) / 153n;
|
|
1334
|
+
const day = doy - (153n * mp + 2n) / 5n + 1n;
|
|
1335
|
+
const month = mp < 10n ? mp + 3n : mp - 9n;
|
|
1336
|
+
if (month <= 2n) year += 1n;
|
|
1337
|
+
return { year, month: Number(month), day: Number(day) };
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function isValidXsdYearLex(s) {
|
|
1341
|
+
const body = s.startsWith('-') ? s.slice(1) : s;
|
|
1342
|
+
if (!/^\d{4,}$/.test(body)) return false;
|
|
1343
|
+
return body.length === 4 || body[0] !== '0';
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function canonicalYearLex(year) {
|
|
1347
|
+
if (year < 0n) return `-${(-year).toString().padStart(4, '0')}`;
|
|
1348
|
+
return year.toString().padStart(4, '0');
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function formatDateTimeLocalKey(year, month, day, hour, minute, second, frac) {
|
|
1352
|
+
return `${canonicalYearLex(year)}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}${frac ? `.${frac}` : ''}`;
|
|
1321
1353
|
}
|
|
1322
1354
|
|
|
1323
1355
|
function parseDateTimeDatatypeValue(lexical, dt) {
|
|
1324
1356
|
const s = String(lexical);
|
|
1325
1357
|
const m = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$/.exec(s);
|
|
1326
|
-
if (!m) return null;
|
|
1358
|
+
if (!m || !isValidXsdYearLex(m[1])) return null;
|
|
1327
1359
|
|
|
1328
|
-
const year =
|
|
1329
|
-
|
|
1360
|
+
const year = BigInt(m[1]);
|
|
1361
|
+
let month = Number(m[2]);
|
|
1330
1362
|
let day = Number(m[3]);
|
|
1331
1363
|
let hour = Number(m[4]);
|
|
1332
1364
|
const minute = Number(m[5]);
|
|
1333
1365
|
const second = Number(m[6]);
|
|
1334
1366
|
let frac = m[7] || '';
|
|
1335
1367
|
const tz = m[8] || null;
|
|
1336
|
-
if (!Number.isSafeInteger(year)) return null;
|
|
1337
1368
|
if (dt === XSD_DATETIME_STAMP_DT && !tz) return null;
|
|
1338
1369
|
if (!(month >= 1 && month <= 12)) return null;
|
|
1339
|
-
if (!(day >= 1 && day <=
|
|
1370
|
+
if (!(day >= 1 && day <= daysInMonthBigInt(year, month))) return null;
|
|
1340
1371
|
if (!(minute >= 0 && minute <= 59)) return null;
|
|
1341
1372
|
if (!(second >= 0 && second <= 59)) return null;
|
|
1373
|
+
|
|
1374
|
+
let dayCount = daysFromCivilBigInt(year, month, day);
|
|
1375
|
+
let normalizedYear = year;
|
|
1342
1376
|
if (hour === 24) {
|
|
1343
1377
|
if (minute !== 0 || second !== 0 || /[1-9]/.test(frac)) return null;
|
|
1344
1378
|
hour = 0;
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1379
|
+
dayCount += 1n;
|
|
1380
|
+
const civil = civilFromDaysBigInt(dayCount);
|
|
1381
|
+
normalizedYear = civil.year;
|
|
1382
|
+
month = civil.month;
|
|
1383
|
+
day = civil.day;
|
|
1349
1384
|
} else if (!(hour >= 0 && hour <= 23)) {
|
|
1350
1385
|
return null;
|
|
1351
1386
|
}
|
|
@@ -1368,11 +1403,10 @@ function parseDateTimeDatatypeValue(lexical, dt) {
|
|
|
1368
1403
|
}
|
|
1369
1404
|
}
|
|
1370
1405
|
|
|
1371
|
-
|
|
1372
|
-
let totalSeconds = BigInt(dayCount) * 86400n + BigInt(hour * 3600 + minute * 60 + second);
|
|
1406
|
+
let totalSeconds = dayCount * 86400n + BigInt(hour * 3600 + minute * 60 + second);
|
|
1373
1407
|
if (offsetMinutes !== null) totalSeconds -= BigInt(offsetMinutes * 60);
|
|
1374
1408
|
|
|
1375
|
-
const localKey =
|
|
1409
|
+
const localKey = formatDateTimeLocalKey(normalizedYear, month, day, hour, minute, second, frac);
|
|
1376
1410
|
const canonicalLex = canonicalDateTimeLex(totalSeconds, fracNum, fracScale, offsetMinutes !== null, localKey);
|
|
1377
1411
|
return {
|
|
1378
1412
|
family: 'dateTime',
|
|
@@ -1392,15 +1426,14 @@ function canonicalDateTimeLex(totalSeconds, fracNum, fracScale, hasTimezone, loc
|
|
|
1392
1426
|
const frac = fracScale > 0 ? `.${fracNum.toString().padStart(fracScale, '0')}` : '';
|
|
1393
1427
|
if (!hasTimezone) return localKey;
|
|
1394
1428
|
|
|
1395
|
-
const
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
return iso;
|
|
1429
|
+
const secondsPerDay = 86400n;
|
|
1430
|
+
const dayCount = floorDivBigInt(totalSeconds, secondsPerDay);
|
|
1431
|
+
const secondOfDay = totalSeconds - dayCount * secondsPerDay;
|
|
1432
|
+
const civil = civilFromDaysBigInt(dayCount);
|
|
1433
|
+
const hour = Number(secondOfDay / 3600n);
|
|
1434
|
+
const minute = Number((secondOfDay % 3600n) / 60n);
|
|
1435
|
+
const second = Number(secondOfDay % 60n);
|
|
1436
|
+
return formatDateTimeLocalKey(civil.year, civil.month, civil.day, hour, minute, second, frac) + 'Z';
|
|
1404
1437
|
}
|
|
1405
1438
|
|
|
1406
1439
|
function parseLangStringDatatypeValue(t, lexical) {
|
|
@@ -1518,7 +1551,31 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1518
1551
|
return evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1519
1552
|
}
|
|
1520
1553
|
|
|
1554
|
+
function booleanTermValue(t) {
|
|
1555
|
+
const info = parseBooleanLiteralInfo(t);
|
|
1556
|
+
return info ? info.value : null;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function makeBooleanLiteral(value) {
|
|
1560
|
+
return internLiteral(value ? 'true' : 'false');
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function evalDatatypeValidityBooleanResult(outTerm, result, subst) {
|
|
1564
|
+
if (outTerm instanceof Var || outTerm instanceof Blank) return evalBindBuiltinObject(outTerm, makeBooleanLiteral(result), subst);
|
|
1565
|
+
const expected = booleanTermValue(outTerm);
|
|
1566
|
+
return expected === result ? [__cloneSubst(subst)] : [];
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1521
1569
|
function evalDatatypeValidityBuiltin(g, subst, positive) {
|
|
1570
|
+
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
1571
|
+
const [lit, dtTerm] = g.s.elems;
|
|
1572
|
+
if (!(lit instanceof Literal) || !(dtTerm instanceof Iri)) return [];
|
|
1573
|
+
const dt = dtTerm.value;
|
|
1574
|
+
if (!isSupportedDatatypeIri(dt)) return [];
|
|
1575
|
+
const ok = parseDatatypeValueForDatatype(lit, dt) !== null;
|
|
1576
|
+
return evalDatatypeValidityBooleanResult(g.o, positive ? ok : !ok, subst);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1522
1579
|
if (!(g.s instanceof Literal)) return [];
|
|
1523
1580
|
|
|
1524
1581
|
let dt = null;
|
|
@@ -1712,8 +1769,9 @@ function formatNum(n) {
|
|
|
1712
1769
|
return String(n);
|
|
1713
1770
|
}
|
|
1714
1771
|
|
|
1715
|
-
function parseXsdDecimalToBigIntScale(s) {
|
|
1716
|
-
let t = String(s)
|
|
1772
|
+
function parseXsdDecimalToBigIntScale(s, options = null) {
|
|
1773
|
+
let t = String(s);
|
|
1774
|
+
if (!options || options.trim !== false) t = t.trim();
|
|
1717
1775
|
let sign = 1n;
|
|
1718
1776
|
|
|
1719
1777
|
if (t.startsWith('+')) t = t.slice(1);
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -66,29 +66,12 @@ function reasonQuiet(opt, input) {
|
|
|
66
66
|
throw err;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const
|
|
70
|
-
const C = TTY
|
|
71
|
-
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
72
|
-
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
73
|
-
|
|
74
|
-
function ok(msg) {
|
|
75
|
-
console.log(`${C.g}OK ${C.n} ${msg}`);
|
|
76
|
-
}
|
|
77
|
-
function info(msg) {
|
|
78
|
-
console.log(`${C.y}==${C.n} ${msg}`);
|
|
79
|
-
}
|
|
80
|
-
function fail(msg) {
|
|
81
|
-
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
82
|
-
}
|
|
69
|
+
const { detail, failResult, info, pass } = require('./report');
|
|
83
70
|
|
|
84
71
|
function unnumberedName(name) {
|
|
85
72
|
return String(name).replace(/^\d+[a-z]*\s+/i, '');
|
|
86
73
|
}
|
|
87
74
|
|
|
88
|
-
function numberedName(index, name) {
|
|
89
|
-
return `${String(index + 1).padStart(3, '0')} ${unnumberedName(name)}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
75
|
function msNow() {
|
|
93
76
|
return Date.now();
|
|
94
77
|
}
|
|
@@ -3172,7 +3155,8 @@ let failed = 0;
|
|
|
3172
3155
|
info(`Running ${cases.length} API tests (independent of examples/)`);
|
|
3173
3156
|
|
|
3174
3157
|
for (const [index, tc] of cases.entries()) {
|
|
3175
|
-
const
|
|
3158
|
+
const testNr = index + 1;
|
|
3159
|
+
const testName = unnumberedName(tc.name);
|
|
3176
3160
|
const start = msNow();
|
|
3177
3161
|
try {
|
|
3178
3162
|
const out = typeof tc.run === 'function' ? await tc.run() : reasonQuiet(tc.opt, tc.input);
|
|
@@ -3187,19 +3171,19 @@ let failed = 0;
|
|
|
3187
3171
|
if (typeof tc.check === 'function') tc.check(out, tc);
|
|
3188
3172
|
|
|
3189
3173
|
const dur = msNow() - start;
|
|
3190
|
-
|
|
3174
|
+
pass(testNr, testName, dur);
|
|
3191
3175
|
passed++;
|
|
3192
3176
|
} catch (e) {
|
|
3193
3177
|
const dur = msNow() - start;
|
|
3194
3178
|
|
|
3195
3179
|
if (tc.expectErrorCode != null) {
|
|
3196
3180
|
if (e && typeof e === 'object' && 'code' in e && e.code === tc.expectErrorCode) {
|
|
3197
|
-
|
|
3181
|
+
pass(testNr, `${testName} (expected exit ${tc.expectErrorCode})`, dur);
|
|
3198
3182
|
passed++;
|
|
3199
3183
|
continue;
|
|
3200
3184
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3185
|
+
failResult(testNr, testName, dur);
|
|
3186
|
+
detail(
|
|
3203
3187
|
`Expected exit code ${tc.expectErrorCode}, got: ${e && e.code != null ? e.code : 'unknown'}\n${
|
|
3204
3188
|
e && e.stderr ? e.stderr : e && e.stack ? e.stack : String(e)
|
|
3205
3189
|
}`,
|
|
@@ -3209,26 +3193,26 @@ let failed = 0;
|
|
|
3209
3193
|
}
|
|
3210
3194
|
|
|
3211
3195
|
if (tc.expectError) {
|
|
3212
|
-
|
|
3196
|
+
pass(testNr, `${testName} (expected error)`, dur);
|
|
3213
3197
|
passed++;
|
|
3214
3198
|
continue;
|
|
3215
3199
|
}
|
|
3216
3200
|
|
|
3217
|
-
|
|
3218
|
-
|
|
3201
|
+
failResult(testNr, testName, dur);
|
|
3202
|
+
detail(e && e.stack ? e.stack : String(e));
|
|
3219
3203
|
failed++;
|
|
3220
3204
|
}
|
|
3221
3205
|
}
|
|
3222
3206
|
|
|
3223
3207
|
console.log('');
|
|
3224
3208
|
const suiteMs = Date.now() - suiteStart;
|
|
3225
|
-
|
|
3209
|
+
info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
|
|
3226
3210
|
|
|
3227
3211
|
if (failed === 0) {
|
|
3228
|
-
|
|
3212
|
+
info(`All API tests passed (${passed}/${cases.length})`);
|
|
3229
3213
|
process.exit(0);
|
|
3230
3214
|
} else {
|
|
3231
|
-
|
|
3215
|
+
info(`Some API tests failed (${passed}/${cases.length})`);
|
|
3232
3216
|
process.exit(1);
|
|
3233
3217
|
}
|
|
3234
3218
|
})();
|
package/test/builtins.test.js
CHANGED
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert/strict');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const C = TTY
|
|
7
|
-
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
8
|
-
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
9
|
-
|
|
10
|
-
function ok(msg) {
|
|
11
|
-
console.log(`${C.g}OK ${C.n} ${msg}`);
|
|
12
|
-
}
|
|
13
|
-
function info(msg) {
|
|
14
|
-
console.log(`${C.y}==${C.n} ${msg}`);
|
|
15
|
-
}
|
|
16
|
-
function fail(msg) {
|
|
17
|
-
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
18
|
-
}
|
|
5
|
+
const { detail, failResult, info, pass } = require('./report');
|
|
19
6
|
|
|
20
7
|
const builtins = require('../lib/builtins');
|
|
21
8
|
require('../lib/engine');
|
|
@@ -183,17 +170,25 @@ const cases = [
|
|
|
183
170
|
{ "2147483648"^^xsd:int dt:invalidForDatatype xsd:int . } => { :invalid :int true } .
|
|
184
171
|
{ "2"^^xsd:boolean dt:invalidForDatatype xsd:boolean . } => { :invalid :boolean true } .
|
|
185
172
|
{ "2026-02-31T00:00:00Z"^^xsd:dateTime dt:invalidForDatatype xsd:dateTime . } => { :invalid :dateTime true } .
|
|
173
|
+
{ " 1.0 "^^xsd:decimal dt:invalidForDatatype xsd:decimal . } => { :invalid :decimalWhitespace true } .
|
|
174
|
+
{ "02026-06-10T12:00:00Z"^^xsd:dateTime dt:invalidForDatatype xsd:dateTime . } => { :invalid :dateTimeYear true } .
|
|
186
175
|
|
|
187
176
|
{ "01"^^xsd:integer dt:sameValueAs "1.0"^^xsd:decimal . } => { :same :numeric true } .
|
|
188
177
|
{ "true"^^xsd:boolean dt:sameValueAs "1"^^xsd:boolean . } => { :same :boolean true } .
|
|
189
178
|
{ "2026-06-10T12:00:00Z"^^xsd:dateTime dt:sameValueAs "2026-06-10T14:00:00+02:00"^^xsd:dateTime . } => { :same :dateTime true } .
|
|
179
|
+
{ "2026-12-31T24:00:00Z"^^xsd:dateTime dt:sameValueAs "2027-01-01T00:00:00Z"^^xsd:dateTime . } => { :same :midnightRollover true } .
|
|
190
180
|
{ "AQID"^^xsd:base64Binary dt:sameValueAs "010203"^^xsd:hexBinary . } => { :same :binary true } .
|
|
191
181
|
{ "11"^^xsd:integer dt:differentValueFrom "12"^^xsd:integer . } => { :different :numeric true } .
|
|
192
182
|
|
|
183
|
+
{ ("1"^^xsd:integer xsd:integer) dt:validForDatatype true . } => { :tuple :valid true } .
|
|
184
|
+
{ ("abc"^^xsd:integer xsd:integer) dt:validForDatatype false . } => { :tuple :invalidBoolean true } .
|
|
185
|
+
{ ("abc"^^xsd:integer xsd:integer) dt:invalidForDatatype ?invalid . } => { :tuple :invalidResult ?invalid } .
|
|
186
|
+
|
|
193
187
|
{ "01"^^xsd:integer dt:canonicalLiteral ?ci . } => { :canonical :integer ?ci } .
|
|
194
188
|
{ "1"^^xsd:boolean dt:canonicalLiteral ?cb . } => { :canonical :boolean ?cb } .
|
|
195
189
|
{ " a\t b "^^xsd:token dt:canonicalLiteral ?ct . } => { :canonical :token ?ct } .
|
|
196
190
|
{ "2026-06-10T14:00:00+02:00"^^xsd:dateTime dt:canonicalLiteral ?cd . } => { :canonical :dateTime ?cd } .
|
|
191
|
+
{ "2026-12-31T24:00:00Z"^^xsd:dateTime dt:canonicalLiteral ?cm . } => { :canonical :midnightRollover ?cm } .
|
|
197
192
|
`);
|
|
198
193
|
|
|
199
194
|
assert.match(out, /:integer :datatype xsd:integer \./);
|
|
@@ -205,15 +200,22 @@ const cases = [
|
|
|
205
200
|
assert.match(out, /:invalid :int true \./);
|
|
206
201
|
assert.match(out, /:invalid :boolean true \./);
|
|
207
202
|
assert.match(out, /:invalid :dateTime true \./);
|
|
203
|
+
assert.match(out, /:invalid :decimalWhitespace true \./);
|
|
204
|
+
assert.match(out, /:invalid :dateTimeYear true \./);
|
|
208
205
|
assert.match(out, /:same :numeric true \./);
|
|
209
206
|
assert.match(out, /:same :boolean true \./);
|
|
210
207
|
assert.match(out, /:same :dateTime true \./);
|
|
208
|
+
assert.match(out, /:same :midnightRollover true \./);
|
|
211
209
|
assert.match(out, /:same :binary true \./);
|
|
212
210
|
assert.match(out, /:different :numeric true \./);
|
|
211
|
+
assert.match(out, /:tuple :valid true \./);
|
|
212
|
+
assert.match(out, /:tuple :invalidBoolean true \./);
|
|
213
|
+
assert.match(out, /:tuple :invalidResult true \./);
|
|
213
214
|
assert.match(out, /:canonical :integer "1"\^\^xsd:integer \./);
|
|
214
215
|
assert.match(out, /:canonical :boolean true \./);
|
|
215
216
|
assert.match(out, /:canonical :token "a b"\^\^xsd:token \./);
|
|
216
217
|
assert.match(out, /:canonical :dateTime "2026-06-10T12:00:00Z"\^\^xsd:dateTime \./);
|
|
218
|
+
assert.match(out, /:canonical :midnightRollover "2027-01-01T00:00:00Z"\^\^xsd:dateTime \./);
|
|
217
219
|
},
|
|
218
220
|
},
|
|
219
221
|
|
|
@@ -226,25 +228,25 @@ let failed = 0;
|
|
|
226
228
|
const suiteStart = Date.now();
|
|
227
229
|
info(`Running ${cases.length} builtin contract tests`);
|
|
228
230
|
|
|
229
|
-
for (const tc of cases) {
|
|
231
|
+
for (const [index, tc] of cases.entries()) {
|
|
230
232
|
const start = Date.now();
|
|
231
233
|
try {
|
|
232
234
|
tc.run();
|
|
233
|
-
|
|
235
|
+
pass(index + 1, tc.name, Date.now() - start);
|
|
234
236
|
passed++;
|
|
235
237
|
} catch (e) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
failResult(index + 1, tc.name, Date.now() - start);
|
|
239
|
+
detail(e && e.stack ? e.stack : String(e));
|
|
238
240
|
failed++;
|
|
239
241
|
}
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
console.log('');
|
|
243
|
-
|
|
245
|
+
info(`Total elapsed: ${Date.now() - suiteStart} ms`);
|
|
244
246
|
if (failed === 0) {
|
|
245
|
-
|
|
247
|
+
info(`All builtin contract tests passed (${passed}/${cases.length})`);
|
|
246
248
|
process.exit(0);
|
|
247
249
|
}
|
|
248
|
-
|
|
250
|
+
info(`Some builtin contract tests failed (${passed}/${cases.length})`);
|
|
249
251
|
process.exit(1);
|
|
250
252
|
})();
|
package/test/examples.test.js
CHANGED
|
@@ -6,21 +6,7 @@ const os = require('node:os');
|
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const cp = require('node:child_process');
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
const C = TTY
|
|
11
|
-
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
12
|
-
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
13
|
-
const msTag = (ms) => `${C.dim}(${ms} ms)${C.n}`;
|
|
14
|
-
|
|
15
|
-
function ok(msg) {
|
|
16
|
-
console.log(`${C.g}OK${C.n} ${msg}`);
|
|
17
|
-
}
|
|
18
|
-
function fail(msg) {
|
|
19
|
-
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
20
|
-
}
|
|
21
|
-
function info(msg) {
|
|
22
|
-
console.log(`${C.y}==${C.n} ${msg}`);
|
|
23
|
-
}
|
|
9
|
+
const { C, detail, failResult, info, pass } = require('./report');
|
|
24
10
|
|
|
25
11
|
function run(cmd, args, opts = {}) {
|
|
26
12
|
return cp.spawnSync(cmd, args, {
|
|
@@ -224,11 +210,11 @@ function main() {
|
|
|
224
210
|
const nodePath = process.execPath;
|
|
225
211
|
|
|
226
212
|
if (!fs.existsSync(examplesDir)) {
|
|
227
|
-
|
|
213
|
+
failResult(1, `Cannot find examples directory: ${examplesDir}`, 0);
|
|
228
214
|
process.exit(1);
|
|
229
215
|
}
|
|
230
216
|
if (!fs.existsSync(eyelingJsPath)) {
|
|
231
|
-
|
|
217
|
+
failResult(1, `Cannot find eyeling.js: ${eyelingJsPath}`, 0);
|
|
232
218
|
process.exit(1);
|
|
233
219
|
}
|
|
234
220
|
|
|
@@ -252,21 +238,19 @@ function main() {
|
|
|
252
238
|
console.log(`${C.dim}${getEyelingVersion(nodePath, eyelingJsPath, root)}; node ${process.version}${C.n}`);
|
|
253
239
|
|
|
254
240
|
if (totalTests === 0) {
|
|
255
|
-
|
|
241
|
+
info(proofOnly ? 'No proof goldens found in examples/proof/' : 'No .n3 files found in examples/');
|
|
256
242
|
process.exit(0);
|
|
257
243
|
}
|
|
258
244
|
|
|
259
245
|
let passed = 0;
|
|
260
246
|
let failed = 0;
|
|
261
247
|
|
|
262
|
-
|
|
263
|
-
const idxWidth = String(files.length).length;
|
|
264
|
-
const proofIdxWidth = String(Math.max(proofFiles.length, 1)).length;
|
|
265
|
-
const proofLabel = proofOnly ? '' : 'proof ';
|
|
248
|
+
let sequence = 0;
|
|
266
249
|
|
|
267
250
|
for (let i = 0; i < files.length; i++) {
|
|
268
|
-
const
|
|
251
|
+
const testNr = ++sequence;
|
|
269
252
|
const file = files[i];
|
|
253
|
+
const testName = file;
|
|
270
254
|
|
|
271
255
|
const start = Date.now();
|
|
272
256
|
|
|
@@ -278,8 +262,8 @@ function main() {
|
|
|
278
262
|
n3Text = fs.readFileSync(filePath, 'utf8');
|
|
279
263
|
} catch (e) {
|
|
280
264
|
const ms = Date.now() - start;
|
|
281
|
-
|
|
282
|
-
|
|
265
|
+
failResult(testNr, testName, ms);
|
|
266
|
+
detail(`Cannot read input: ${e.message}`);
|
|
283
267
|
failed++;
|
|
284
268
|
continue;
|
|
285
269
|
}
|
|
@@ -291,8 +275,8 @@ function main() {
|
|
|
291
275
|
// comparable across environments.
|
|
292
276
|
if (!fs.existsSync(expectedPath)) {
|
|
293
277
|
const ms = Date.now() - start;
|
|
294
|
-
|
|
295
|
-
|
|
278
|
+
failResult(testNr, testName, ms);
|
|
279
|
+
detail(`Missing expected output for ${path.basename(file, path.extname(file))}.*`);
|
|
296
280
|
failed++;
|
|
297
281
|
continue;
|
|
298
282
|
}
|
|
@@ -324,18 +308,18 @@ function main() {
|
|
|
324
308
|
|
|
325
309
|
if (diffOk && rcOk) {
|
|
326
310
|
if (expectedRc === 0) {
|
|
327
|
-
|
|
311
|
+
pass(testNr, testName, ms);
|
|
328
312
|
} else {
|
|
329
|
-
|
|
313
|
+
pass(testNr, `${testName} (expected exit ${expectedRc})`, ms);
|
|
330
314
|
}
|
|
331
315
|
passed++;
|
|
332
316
|
} else {
|
|
333
|
-
|
|
317
|
+
failResult(testNr, testName, ms);
|
|
334
318
|
if (!rcOk) {
|
|
335
|
-
|
|
319
|
+
detail(`Exit code ${rc}, expected ${expectedRc}`);
|
|
336
320
|
}
|
|
337
321
|
if (!diffOk) {
|
|
338
|
-
|
|
322
|
+
detail('Output differs');
|
|
339
323
|
}
|
|
340
324
|
|
|
341
325
|
// Show diffs, because this is a test runner
|
|
@@ -353,8 +337,9 @@ function main() {
|
|
|
353
337
|
|
|
354
338
|
|
|
355
339
|
for (let i = 0; i < proofFiles.length; i++) {
|
|
356
|
-
const
|
|
340
|
+
const testNr = ++sequence;
|
|
357
341
|
const file = proofFiles[i];
|
|
342
|
+
const testName = proofOnly ? file : `proof ${file}`;
|
|
358
343
|
const start = Date.now();
|
|
359
344
|
const filePath = path.join(examplesDir, file);
|
|
360
345
|
const expectedPath = path.join(proofDir, file);
|
|
@@ -364,8 +349,8 @@ function main() {
|
|
|
364
349
|
n3Text = fs.readFileSync(filePath, 'utf8');
|
|
365
350
|
} catch (e) {
|
|
366
351
|
const ms = Date.now() - start;
|
|
367
|
-
|
|
368
|
-
|
|
352
|
+
failResult(testNr, testName, ms);
|
|
353
|
+
detail(`Cannot read proof input: ${e.message}`);
|
|
369
354
|
failed++;
|
|
370
355
|
continue;
|
|
371
356
|
}
|
|
@@ -388,18 +373,18 @@ function main() {
|
|
|
388
373
|
|
|
389
374
|
if (diffOk && rcOk) {
|
|
390
375
|
if (expectedRc === 0) {
|
|
391
|
-
|
|
376
|
+
pass(testNr, testName, ms);
|
|
392
377
|
} else {
|
|
393
|
-
|
|
378
|
+
pass(testNr, `${testName} (expected exit ${expectedRc})`, ms);
|
|
394
379
|
}
|
|
395
380
|
passed++;
|
|
396
381
|
} else {
|
|
397
|
-
|
|
382
|
+
failResult(testNr, testName, ms);
|
|
398
383
|
if (!rcOk) {
|
|
399
|
-
|
|
384
|
+
detail(`Exit code ${rc}, expected ${expectedRc}`);
|
|
400
385
|
}
|
|
401
386
|
if (!diffOk) {
|
|
402
|
-
|
|
387
|
+
detail('Proof output differs');
|
|
403
388
|
}
|
|
404
389
|
showDiff({
|
|
405
390
|
examplesDir,
|
|
@@ -417,12 +402,12 @@ function main() {
|
|
|
417
402
|
info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
|
|
418
403
|
|
|
419
404
|
if (failed === 0) {
|
|
420
|
-
|
|
405
|
+
info(proofOnly
|
|
421
406
|
? `All proof golden tests passed (${passed}/${totalTests})`
|
|
422
407
|
: `All examples tests passed (${passed}/${totalTests})`);
|
|
423
408
|
process.exit(0);
|
|
424
409
|
} else {
|
|
425
|
-
|
|
410
|
+
info(proofOnly
|
|
426
411
|
? `Some proof golden tests failed (${passed}/${totalTests})`
|
|
427
412
|
: `Some examples tests failed (${passed}/${totalTests})`);
|
|
428
413
|
// keep exit code 2 (matches historical behavior of examples/test)
|
package/test/extra.test.js
CHANGED
|
@@ -5,21 +5,7 @@ const fs = require('node:fs');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const cp = require('node:child_process');
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const C = TTY
|
|
10
|
-
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
11
|
-
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
12
|
-
const msTag = (ms) => `${C.dim}(${ms} ms)${C.n}`;
|
|
13
|
-
|
|
14
|
-
function ok(msg) {
|
|
15
|
-
console.log(`${C.g}OK${C.n} ${msg}`);
|
|
16
|
-
}
|
|
17
|
-
function fail(msg) {
|
|
18
|
-
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
19
|
-
}
|
|
20
|
-
function info(msg) {
|
|
21
|
-
console.log(`${C.y}==${C.n} ${msg}`);
|
|
22
|
-
}
|
|
8
|
+
const { C, detail, failResult, info, pass } = require('./report');
|
|
23
9
|
|
|
24
10
|
function main() {
|
|
25
11
|
const suiteStart = Date.now();
|
|
@@ -29,7 +15,7 @@ function main() {
|
|
|
29
15
|
const nodePath = process.execPath;
|
|
30
16
|
|
|
31
17
|
if (!fs.existsSync(extraDir)) {
|
|
32
|
-
|
|
18
|
+
failResult(1, `Cannot find examples/extra directory: ${extraDir}`, 0);
|
|
33
19
|
process.exit(1);
|
|
34
20
|
}
|
|
35
21
|
|
|
@@ -44,16 +30,14 @@ function main() {
|
|
|
44
30
|
console.log(`${C.dim}node ${process.version}${C.n}`);
|
|
45
31
|
|
|
46
32
|
if (files.length === 0) {
|
|
47
|
-
|
|
33
|
+
info('No .js files found in examples/extra/');
|
|
48
34
|
process.exit(0);
|
|
49
35
|
}
|
|
50
36
|
|
|
51
37
|
let passed = 0;
|
|
52
38
|
let failed = 0;
|
|
53
|
-
const idxWidth = String(files.length).length;
|
|
54
|
-
|
|
55
39
|
for (let i = 0; i < files.length; i += 1) {
|
|
56
|
-
const
|
|
40
|
+
const testNr = i + 1;
|
|
57
41
|
const file = files[i];
|
|
58
42
|
const start = Date.now();
|
|
59
43
|
|
|
@@ -74,11 +58,11 @@ function main() {
|
|
|
74
58
|
const ms = Date.now() - start;
|
|
75
59
|
|
|
76
60
|
if (rc === 0) {
|
|
77
|
-
|
|
61
|
+
pass(testNr, `${file} -> output/${path.basename(outputPath)}`, ms);
|
|
78
62
|
passed += 1;
|
|
79
63
|
} else {
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
failResult(testNr, file, ms);
|
|
65
|
+
detail(`Exit code ${rc}`);
|
|
82
66
|
if (r.stderr) process.stderr.write(r.stderr);
|
|
83
67
|
failed += 1;
|
|
84
68
|
}
|
|
@@ -89,11 +73,11 @@ function main() {
|
|
|
89
73
|
info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
|
|
90
74
|
|
|
91
75
|
if (failed === 0) {
|
|
92
|
-
|
|
76
|
+
info(`All extra examples passed (${passed}/${files.length})`);
|
|
93
77
|
process.exit(0);
|
|
94
78
|
}
|
|
95
79
|
|
|
96
|
-
|
|
80
|
+
info(`Some extra examples failed (${passed}/${files.length})`);
|
|
97
81
|
process.exit(2);
|
|
98
82
|
}
|
|
99
83
|
|