eyeling 1.5.15 → 1.5.17
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 +84 -63
- package/package.json +6 -4
- package/test/api.test.js +3 -0
- package/test/examples.test.js +242 -0
- package/test/package.test.js +132 -0
- package/test/packlist.test.js +0 -1
- package/examples/test +0 -154
- package/test/package-smoke.sh +0 -55
package/README.md
CHANGED
|
@@ -4,12 +4,13 @@ A minimal [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
|
4
4
|
|
|
5
5
|
`eyeling` is:
|
|
6
6
|
|
|
7
|
-
- a single self-contained file (`eyeling.js`, no external deps)
|
|
8
|
-
- intentionally tiny and close in spirit to EYE
|
|
9
|
-
- a practical N3/Turtle superset (enough for lots of real rulesets)
|
|
10
|
-
- supports forward (`=>`) + backward (`<=`) chaining over Horn-style rules
|
|
11
|
-
- prints only newly derived forward facts, optionally preceded by compact proof comments
|
|
12
|
-
- we never want to leak raw data, hence pass-only-new and backward rules for functions that work with raw data
|
|
7
|
+
- a single self-contained file (`eyeling.js`, no external deps)
|
|
8
|
+
- intentionally tiny and close in spirit to EYE
|
|
9
|
+
- a practical N3/Turtle superset (enough for lots of real rulesets)
|
|
10
|
+
- supports forward (`=>`) + backward (`<=`) chaining over Horn-style rules
|
|
11
|
+
- prints only newly derived forward facts, optionally preceded by compact proof comments
|
|
12
|
+
- we never want to leak raw data, hence pass-only-new and backward rules for functions that work with raw data
|
|
13
|
+
- and of course we also keep all reasoning in the browser
|
|
13
14
|
|
|
14
15
|
## Playground (in your browser)
|
|
15
16
|
|
|
@@ -18,21 +19,22 @@ Try it here:
|
|
|
18
19
|
- [Eyeling playground](https://eyereasoner.github.io/eyeling/demo)
|
|
19
20
|
|
|
20
21
|
The playground runs `eyeling` client-side. You can:
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
22
|
+
|
|
23
|
+
- edit an N3 program directly
|
|
24
|
+
- load an N3 program from a URL
|
|
25
|
+
- share a link with the program encoded in the URL fragment (`#...`)
|
|
24
26
|
|
|
25
27
|
### Example (Socrates)
|
|
26
28
|
|
|
27
29
|
This link preloads a small “Socrates is Mortal” ruleset:
|
|
28
30
|
|
|
29
|
-
[Socrates example](https://eyereasoner.github.io/eyeling/demo#%23%20------------------%0A%23%20Socrates%20inference%0A%23%20------------------%0A%0A%40prefix%20rdfs%3A%20%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E.%0A%40prefix%20%3A%20%3Chttp%3A%2F%2Fexample.org%2Fsocrates%23%3E.%0A%0A%23%20facts%0A%3ASocrates%20a%20%3AHuman.%0A%3AHuman%20rdfs%3AsubClassOf%20%3AMortal.%0A%0A%23%20subclass%20rule%0A%7B%0A%20%20%20%20%3FS%20a%20%3FA.%0A%20%20%20%20%3FA%20rdfs%3AsubClassOf%20%3FB.%0A%7D%20%3D%3E%20%7B%0A%20%20%20%20%3FS%20a%20%3FB.%0A%7D%2E%0A)
|
|
31
|
+
- [Socrates example](https://eyereasoner.github.io/eyeling/demo#%23%20------------------%0A%23%20Socrates%20inference%0A%23%20------------------%0A%0A%40prefix%20rdfs%3A%20%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E.%0A%40prefix%20%3A%20%3Chttp%3A%2F%2Fexample.org%2Fsocrates%23%3E.%0A%0A%23%20facts%0A%3ASocrates%20a%20%3AHuman.%0A%3AHuman%20rdfs%3AsubClassOf%20%3AMortal.%0A%0A%23%20subclass%20rule%0A%7B%0A%20%20%20%20%3FS%20a%20%3FA.%0A%20%20%20%20%3FA%20rdfs%3AsubClassOf%20%3FB.%0A%7D%20%3D%3E%20%7B%0A%20%20%20%20%3FS%20a%20%3FB.%0A%7D%2E%0A)
|
|
30
32
|
|
|
31
33
|
## Quick start (Node.js)
|
|
32
34
|
|
|
33
35
|
### Requirements
|
|
34
36
|
|
|
35
|
-
-
|
|
37
|
+
- Node.js >= 18 (anything modern with `BigInt` support is fine)
|
|
36
38
|
|
|
37
39
|
### Install (npm)
|
|
38
40
|
|
|
@@ -53,7 +55,7 @@ npx eyeling examples/socrates.n3
|
|
|
53
55
|
### JavaScript API (Node)
|
|
54
56
|
|
|
55
57
|
```js
|
|
56
|
-
const { reason } = require(
|
|
58
|
+
const { reason } = require("eyeling");
|
|
57
59
|
|
|
58
60
|
const input = `
|
|
59
61
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
|
|
@@ -72,7 +74,7 @@ console.log(output);
|
|
|
72
74
|
ESM:
|
|
73
75
|
|
|
74
76
|
```js
|
|
75
|
-
import eyeling from
|
|
77
|
+
import eyeling from "eyeling";
|
|
76
78
|
|
|
77
79
|
const output = eyeling.reason({ proofComments: false }, input);
|
|
78
80
|
console.log(output);
|
|
@@ -80,6 +82,28 @@ console.log(output);
|
|
|
80
82
|
|
|
81
83
|
Note: the API currently shells out to the bundled `eyeling.js` CLI under the hood (simple + robust).
|
|
82
84
|
|
|
85
|
+
### Testing
|
|
86
|
+
|
|
87
|
+
From a repo checkout:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or run individual suites:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run test:api
|
|
97
|
+
npm run test:examples
|
|
98
|
+
npm run test:package
|
|
99
|
+
npm run test:packlist
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- `test:api` runs an independent JS API test suite (does not rely on `examples/`).
|
|
103
|
+
- `test:examples` runs the examples in `examples` directory and compares against the golden outputs in `examples/output`.
|
|
104
|
+
- `test:package` does a “real consumer” smoke test: `npm pack` → install tarball into a temp project → run API + CLI + examples.
|
|
105
|
+
- `test:packlist` sanity-checks what will be published in the npm tarball (and the CLI shebang/bin wiring).
|
|
106
|
+
|
|
83
107
|
### Run a single file
|
|
84
108
|
|
|
85
109
|
From the repo root:
|
|
@@ -94,10 +118,10 @@ node eyeling.js examples/socrates.n3
|
|
|
94
118
|
|
|
95
119
|
By default, `eyeling`:
|
|
96
120
|
|
|
97
|
-
1. parses the input (facts + rules)
|
|
98
|
-
2. runs **forward chaining to a fixpoint
|
|
99
|
-
3. prints only **newly derived forward facts** (not the original input facts)
|
|
100
|
-
4. prints a compact per-triple explanation as `#` comments (can be disabled)
|
|
121
|
+
1. parses the input (facts + rules)
|
|
122
|
+
2. runs **forward chaining to a fixpoint**
|
|
123
|
+
3. prints only **newly derived forward facts** (not the original input facts)
|
|
124
|
+
4. prints a compact per-triple explanation as `#` comments (can be disabled)
|
|
101
125
|
|
|
102
126
|
### Options
|
|
103
127
|
|
|
@@ -116,7 +140,8 @@ node eyeling.js -n examples/socrates.n3
|
|
|
116
140
|
npm run test:examples
|
|
117
141
|
```
|
|
118
142
|
|
|
119
|
-
This runs `eyeling.js` over each example and compares against the golden outputs in `examples/output
|
|
143
|
+
This runs `eyeling.js` over each example and compares against the golden outputs in `examples/output`
|
|
144
|
+
(works both in a git checkout and in an npm-installed package).
|
|
120
145
|
|
|
121
146
|
## What output do I get?
|
|
122
147
|
|
|
@@ -131,14 +156,14 @@ The proof comments are compact “local justifications” per derived triple (no
|
|
|
131
156
|
|
|
132
157
|
### Forward + backward chaining
|
|
133
158
|
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
- **Forward chaining to fixpoint** for forward rules written as `{ P } => { C } .`
|
|
160
|
+
- **Backward chaining (SLD-style)** for backward rules written as `{ H } <= { B } .` and for built-ins.
|
|
136
161
|
|
|
137
162
|
Forward rule premises are proved using:
|
|
138
163
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
164
|
+
- ground facts (input + derived)
|
|
165
|
+
- backward rules
|
|
166
|
+
- built-ins
|
|
142
167
|
|
|
143
168
|
The CLI prints only newly derived forward facts.
|
|
144
169
|
|
|
@@ -146,63 +171,59 @@ The CLI prints only newly derived forward facts.
|
|
|
146
171
|
|
|
147
172
|
`eyeling` stays tiny, but includes a few key performance mechanisms:
|
|
148
173
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* for very deep backward chains, substitutions may be compactified (semantics-preserving) to avoid quadratic “copy a growing substitution object” behavior.
|
|
174
|
+
- facts are indexed for matching:
|
|
175
|
+
- by predicate, and (when possible) by **(predicate, object)** (important for type-heavy workloads)
|
|
176
|
+
- duplicate detection uses a fast key path when a triple is fully IRI/Literal-shaped
|
|
177
|
+
- backward rules are indexed by head predicate
|
|
178
|
+
- the backward prover is **iterative** (explicit stack), so deep chains won’t blow the JS call stack
|
|
179
|
+
- for very deep backward chains, substitutions may be compactified (semantics-preserving) to avoid quadratic “copy a growing substitution object” behavior
|
|
156
180
|
|
|
157
181
|
## Parsing: practical N3 subset
|
|
158
182
|
|
|
159
183
|
Supported:
|
|
160
184
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
* datatyped literals with `^^`
|
|
175
|
-
* `#` line comments
|
|
185
|
+
- `@prefix` / `@base`
|
|
186
|
+
- triples with `;` and `,`
|
|
187
|
+
- variables `?x`
|
|
188
|
+
- blank nodes:
|
|
189
|
+
- anonymous `[]`
|
|
190
|
+
- property lists `[ :p :o; :q :r ]`
|
|
191
|
+
- collections `( ... )`
|
|
192
|
+
- quoted formulas `{ ... }`
|
|
193
|
+
- implications:
|
|
194
|
+
- forward rules `{ P } => { C } .`
|
|
195
|
+
- backward rules `{ H } <= { B } .`
|
|
196
|
+
- datatyped literals with `^^`
|
|
197
|
+
- `#` line comments
|
|
176
198
|
|
|
177
199
|
Non-goals / current limits:
|
|
178
200
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
- not a full W3C N3 grammar (some edge cases for identifiers, quantifiers, advanced syntax)
|
|
202
|
+
- quoted formulas are matched as whole formulas (no pattern matching inside formulas yet)
|
|
203
|
+
- proof output is local per derived triple (not a global exported proof tree)
|
|
182
204
|
|
|
183
205
|
## Blank nodes and quantification (pragmatic N3/EYE-style)
|
|
184
206
|
|
|
185
207
|
`eyeling` follows the usual N3 intuition:
|
|
186
208
|
|
|
187
|
-
1. blank nodes in facts are normal RDF blanks (`_:b1`, `_:b2`, … within a run)
|
|
188
|
-
2. blank nodes in rule premises behave like rule-scoped universals (similar to variables)
|
|
189
|
-
3. blank nodes only in rule conclusions behave like existentials:
|
|
190
|
-
each rule firing generates fresh Skolem blanks (`_:sk_0`, `_:sk_1`, …).
|
|
209
|
+
1. blank nodes in facts are normal RDF blanks (`_:b1`, `_:b2`, … within a run)
|
|
210
|
+
2. blank nodes in rule premises behave like rule-scoped universals (similar to variables)
|
|
211
|
+
3. blank nodes only in rule conclusions behave like existentials: each rule firing generates fresh Skolem blanks (`_:sk_0`, `_:sk_1`, …)
|
|
191
212
|
|
|
192
213
|
Equal facts up to renaming of Skolem IDs are treated as duplicates and are not re-added.
|
|
193
214
|
|
|
194
215
|
## Rule-producing rules (meta-rules)
|
|
195
216
|
|
|
196
|
-
`eyeling` understands the `log:implies` / `log:impliedBy` idiom
|
|
217
|
+
`eyeling` understands the `log:implies` / `log:impliedBy` idiom.
|
|
197
218
|
|
|
198
219
|
Top level:
|
|
199
220
|
|
|
200
|
-
|
|
201
|
-
|
|
221
|
+
- `{ P } log:implies { C } .` becomes a forward rule `{ P } => { C } .`
|
|
222
|
+
- `{ H } log:impliedBy { B } .` becomes a backward rule `{ H } <= { B } .`
|
|
202
223
|
|
|
203
224
|
During reasoning:
|
|
204
225
|
|
|
205
|
-
|
|
226
|
+
- any **derived** `log:implies` / `log:impliedBy` triple with formula subject/object is turned into a new live forward/backward rule.
|
|
206
227
|
|
|
207
228
|
## Inference fuse — `{ ... } => false.`
|
|
208
229
|
|
|
@@ -211,6 +232,7 @@ Rules whose conclusion is `false` are treated as hard failures:
|
|
|
211
232
|
```n3
|
|
212
233
|
:stone :color :black .
|
|
213
234
|
:stone :color :white .
|
|
235
|
+
|
|
214
236
|
{ ?X :color :black . ?X :color :white . } => false.
|
|
215
237
|
```
|
|
216
238
|
|
|
@@ -220,13 +242,12 @@ As soon as the premise is provable, `eyeling` exits with status code `2`.
|
|
|
220
242
|
|
|
221
243
|
`eyeling` implements a pragmatic subset of common N3 builtin families and evaluates them during backward goal proving:
|
|
222
244
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
245
|
+
- **crypto**: `crypto:md5` `crypto:sha` `crypto:sha256` `crypto:sha512`
|
|
246
|
+
- **list**: `list:append` `list:first` `list:firstRest` `list:in` `list:iterate` `list:last` `list:length` `list:map` `list:member` `list:memberAt` `list:notMember` `list:remove` `list:reverse` `list:sort`
|
|
247
|
+
- **log**: `log:collectAllIn` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:notEqualTo` `log:notIncludes` `log:skolem` `log:uri`
|
|
248
|
+
- **math**: `math:absoluteValue` `math:acos` `math:asin` `math:atan` `math:cos` `math:cosh` `math:degrees` `math:difference` `math:equalTo` `math:exponentiation` `math:greaterThan` `math:lessThan` `math:negation` `math:notEqualTo` `math:notGreaterThan` `math:notLessThan` `math:product` `math:quotient` `math:remainder` `math:rounded` `math:sin` `math:sinh` `math:sum` `math:tan` `math:tanh`
|
|
249
|
+
- **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
|
|
250
|
+
- **time**: `time:localTime`
|
|
230
251
|
|
|
231
252
|
## License
|
|
232
253
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeling",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.17",
|
|
4
4
|
"description": "A minimal Notation3 (N3) reasoner in JavaScript.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"test:packlist": "node test/packlist.test.js",
|
|
33
33
|
"test:api": "node test/api.test.js",
|
|
34
|
-
"test:examples": "
|
|
35
|
-
"test:package": "
|
|
36
|
-
"test": "npm run test:packlist && npm run test:api && npm run test:examples"
|
|
34
|
+
"test:examples": "node test/examples.test.js",
|
|
35
|
+
"test:package": "node test/package.test.js",
|
|
36
|
+
"test": "npm run test:packlist && npm run test:api && npm run test:examples",
|
|
37
|
+
"preversion": "npm test",
|
|
38
|
+
"postversion": "git push origin HEAD --follow-tags"
|
|
37
39
|
}
|
|
38
40
|
}
|
package/test/api.test.js
CHANGED
|
@@ -556,6 +556,7 @@ let passed = 0;
|
|
|
556
556
|
let failed = 0;
|
|
557
557
|
|
|
558
558
|
(async function main() {
|
|
559
|
+
const suiteStart = Date.now();
|
|
559
560
|
info(`Running ${cases.length} API tests (independent of examples/)`);
|
|
560
561
|
|
|
561
562
|
for (const tc of cases) {
|
|
@@ -602,6 +603,8 @@ let failed = 0;
|
|
|
602
603
|
}
|
|
603
604
|
|
|
604
605
|
console.log('');
|
|
606
|
+
const suiteMs = Date.now() - suiteStart;
|
|
607
|
+
console.log(`${C.y}==${C.n} Total elapsed: ${suiteMs} ms`);
|
|
605
608
|
if (failed === 0) {
|
|
606
609
|
ok(`All API tests passed (${passed}/${cases.length})`);
|
|
607
610
|
process.exit(0);
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const cp = require('node:child_process');
|
|
8
|
+
|
|
9
|
+
const TTY = process.stdout.isTTY;
|
|
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
|
+
|
|
14
|
+
function ok(msg) { console.log(`${C.g}OK${C.n} ${msg}`); }
|
|
15
|
+
function fail(msg) { console.error(`${C.r}FAIL${C.n} ${msg}`); }
|
|
16
|
+
function info(msg) { console.log(`${C.y}==${C.n} ${msg}`); }
|
|
17
|
+
|
|
18
|
+
function padRight(s, n) {
|
|
19
|
+
s = String(s);
|
|
20
|
+
return s.length >= n ? s : (s + ' '.repeat(n - s.length));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function padLeft(s, n) {
|
|
24
|
+
s = String(s);
|
|
25
|
+
return s.length >= n ? s : (' '.repeat(n - s.length) + s);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function run(cmd, args, opts = {}) {
|
|
29
|
+
return cp.spawnSync(cmd, args, {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
maxBuffer: 200 * 1024 * 1024,
|
|
32
|
+
...opts,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasGit() {
|
|
37
|
+
const r = run('git', ['--version']);
|
|
38
|
+
return r.status === 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function inGitWorktree(cwd) {
|
|
42
|
+
if (!hasGit()) return false;
|
|
43
|
+
const r = run('git', ['rev-parse', '--is-inside-work-tree'], { cwd });
|
|
44
|
+
return r.status === 0 && String(r.stdout).trim() === 'true';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Expectation logic (same as bash version):
|
|
48
|
+
// 1) If file contains a comment like: # expect-exit: 2 -> use that
|
|
49
|
+
// 2) Else, if it contains "=> false" -> expect exit 2
|
|
50
|
+
// 3) Else -> expect exit 0
|
|
51
|
+
function expectedExitCode(n3Text) {
|
|
52
|
+
const m = n3Text.match(/^[ \t]*#[: ]*expect-exit:[ \t]*([0-9]+)\b/m);
|
|
53
|
+
if (m) return parseInt(m[1], 10);
|
|
54
|
+
if (/=>\s*false\b/.test(n3Text)) return 2;
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getEyelingVersion(nodePath, eyelingJsPath, cwd) {
|
|
59
|
+
const r = run(nodePath, [eyelingJsPath, '-v'], { cwd });
|
|
60
|
+
// eyeling prints version to stdout in your CLI
|
|
61
|
+
const s = (r.stdout || r.stderr || '').trim();
|
|
62
|
+
return s || 'eyeling (unknown version)';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function mkTmpFile() {
|
|
66
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-examples-'));
|
|
67
|
+
const file = path.join(dir, 'generated.n3');
|
|
68
|
+
return { dir, file };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function rmrf(p) {
|
|
72
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch {}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
const suiteStart = Date.now();
|
|
77
|
+
|
|
78
|
+
// package root: .../test/examples.test.js -> root is one level up
|
|
79
|
+
const root = path.resolve(__dirname, '..');
|
|
80
|
+
const examplesDir = path.join(root, 'examples');
|
|
81
|
+
const outputDir = path.join(examplesDir, 'output');
|
|
82
|
+
const eyelingJsPath = path.join(root, 'eyeling.js');
|
|
83
|
+
const nodePath = process.execPath;
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(examplesDir)) {
|
|
86
|
+
fail(`Missing examples directory: ${examplesDir}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
if (!fs.existsSync(eyelingJsPath)) {
|
|
90
|
+
fail(`Missing eyeling.js: ${eyelingJsPath}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const IN_GIT = inGitWorktree(root);
|
|
95
|
+
|
|
96
|
+
// Header
|
|
97
|
+
console.log(`${C.y}-------------------------------------------------${C.n}`);
|
|
98
|
+
console.log(`${C.y}running eyeling examples${C.n}`);
|
|
99
|
+
console.log(
|
|
100
|
+
`${C.y}using ${getEyelingVersion(nodePath, eyelingJsPath, root)} and node ${process.version}${C.n}`
|
|
101
|
+
);
|
|
102
|
+
console.log(`${C.y}-------------------------------------------------${C.n}`);
|
|
103
|
+
console.log('');
|
|
104
|
+
|
|
105
|
+
// In maintainer mode we write expected outputs (tracked) to examples/output/
|
|
106
|
+
if (IN_GIT) fs.mkdirSync(outputDir, { recursive: true });
|
|
107
|
+
|
|
108
|
+
const files = fs.readdirSync(examplesDir)
|
|
109
|
+
.filter(f => f.endsWith('.n3'))
|
|
110
|
+
.sort((a, b) => a.localeCompare(b));
|
|
111
|
+
|
|
112
|
+
if (files.length === 0) {
|
|
113
|
+
info('No .n3 files found in examples/');
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let OK = 0;
|
|
118
|
+
let DIFF = 0;
|
|
119
|
+
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const filePath = path.join(examplesDir, file);
|
|
122
|
+
const expectedPath = path.join(outputDir, file); // examples/output/<file>
|
|
123
|
+
|
|
124
|
+
const start = Date.now();
|
|
125
|
+
|
|
126
|
+
let n3Text = '';
|
|
127
|
+
try {
|
|
128
|
+
n3Text = fs.readFileSync(filePath, 'utf8');
|
|
129
|
+
} catch (e) {
|
|
130
|
+
const ms = Date.now() - start;
|
|
131
|
+
process.stdout.write(padRight(file, 36));
|
|
132
|
+
process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
|
|
133
|
+
console.log(`${C.r}DIFF${C.n} (cannot read input: ${e.message})`);
|
|
134
|
+
DIFF++;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const expectedRc = expectedExitCode(n3Text);
|
|
139
|
+
|
|
140
|
+
// Decide where to write generated output
|
|
141
|
+
let tmp = null;
|
|
142
|
+
let generatedPath = expectedPath;
|
|
143
|
+
|
|
144
|
+
if (!IN_GIT) {
|
|
145
|
+
// npm-installed / no .git: never modify output/ in node_modules
|
|
146
|
+
if (!fs.existsSync(expectedPath)) {
|
|
147
|
+
const ms = Date.now() - start;
|
|
148
|
+
process.stdout.write(padRight(file, 36));
|
|
149
|
+
process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
|
|
150
|
+
console.log(`${C.r}MISSING expected output/${file}${C.n}`);
|
|
151
|
+
DIFF++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
tmp = mkTmpFile();
|
|
155
|
+
generatedPath = tmp.file;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Run eyeling, capture exit code without aborting the suite
|
|
159
|
+
// We run `node eyeling.js <file>` from examplesDir so relative paths match old behavior.
|
|
160
|
+
const r = run(nodePath, [eyelingJsPath, file], { cwd: examplesDir });
|
|
161
|
+
const rc = (r.status == null) ? 1 : r.status;
|
|
162
|
+
|
|
163
|
+
// Write stdout to the chosen output file (expected in git mode, tmp in npm mode)
|
|
164
|
+
try {
|
|
165
|
+
fs.writeFileSync(generatedPath, r.stdout || '', 'utf8');
|
|
166
|
+
} catch (e) {
|
|
167
|
+
const ms = Date.now() - start;
|
|
168
|
+
process.stdout.write(padRight(file, 36));
|
|
169
|
+
process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
|
|
170
|
+
console.log(`${C.r}DIFF${C.n} (cannot write output: ${e.message})`);
|
|
171
|
+
DIFF++;
|
|
172
|
+
if (tmp) rmrf(tmp.dir);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const ms = Date.now() - start;
|
|
177
|
+
|
|
178
|
+
// Compare outputs
|
|
179
|
+
let diffOk = false;
|
|
180
|
+
|
|
181
|
+
if (IN_GIT) {
|
|
182
|
+
// Compare expectedPath against HEAD using git diff
|
|
183
|
+
const d = run('git', ['diff', '--quiet', '--', path.posix.join('output', file)], { cwd: examplesDir });
|
|
184
|
+
diffOk = (d.status === 0);
|
|
185
|
+
} else {
|
|
186
|
+
// Compare expectedPath vs generatedPath without needing a repo
|
|
187
|
+
if (hasGit()) {
|
|
188
|
+
const d = run('git', ['diff', '--no-index', '--quiet', expectedPath, generatedPath], { cwd: examplesDir });
|
|
189
|
+
diffOk = (d.status === 0);
|
|
190
|
+
} else {
|
|
191
|
+
const d = run('diff', ['-u', expectedPath, generatedPath], { cwd: examplesDir });
|
|
192
|
+
diffOk = (d.status === 0);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Decide pass/fail
|
|
197
|
+
process.stdout.write(padRight(file, 36));
|
|
198
|
+
process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
|
|
199
|
+
|
|
200
|
+
if (diffOk && rc === expectedRc) {
|
|
201
|
+
if (rc === 0) {
|
|
202
|
+
console.log(`${C.g}OK${C.n}`);
|
|
203
|
+
} else {
|
|
204
|
+
console.log(`${C.g}OK${C.n} (exit ${rc})`);
|
|
205
|
+
}
|
|
206
|
+
OK++;
|
|
207
|
+
} else {
|
|
208
|
+
if (rc !== expectedRc) {
|
|
209
|
+
console.log(`${C.r}DIFF${C.n} (exit ${rc}, expected ${expectedRc})`);
|
|
210
|
+
} else {
|
|
211
|
+
console.log(`${C.r}DIFF${C.n}`);
|
|
212
|
+
}
|
|
213
|
+
DIFF++;
|
|
214
|
+
|
|
215
|
+
// In npm mode, show a diff (nice UX) without modifying node_modules
|
|
216
|
+
if (!IN_GIT) {
|
|
217
|
+
if (hasGit()) {
|
|
218
|
+
const d = run('git', ['diff', '--no-index', expectedPath, generatedPath], { cwd: examplesDir });
|
|
219
|
+
if (d.stdout) process.stdout.write(d.stdout);
|
|
220
|
+
if (d.stderr) process.stderr.write(d.stderr);
|
|
221
|
+
} else {
|
|
222
|
+
const d = run('diff', ['-u', expectedPath, generatedPath], { cwd: examplesDir });
|
|
223
|
+
if (d.stdout) process.stdout.write(d.stdout);
|
|
224
|
+
if (d.stderr) process.stderr.write(d.stderr);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// cleanup tmp file
|
|
230
|
+
if (tmp) rmrf(tmp.dir);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log('');
|
|
234
|
+
const suiteMs = Date.now() - suiteStart;
|
|
235
|
+
console.log(`${C.y}==${C.n} Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
|
|
236
|
+
console.log(`${C.y}==${C.n} ${C.g}${OK} OK${C.n} ${C.r}${DIFF} DIFF${C.n}`);
|
|
237
|
+
|
|
238
|
+
process.exit(DIFF === 0 ? 0 : 2);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
main();
|
|
242
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const cp = require('node:child_process');
|
|
8
|
+
|
|
9
|
+
const TTY = process.stdout.isTTY;
|
|
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
|
+
|
|
14
|
+
function info(msg) { console.log(`${C.y}==${C.n} ${msg}`); }
|
|
15
|
+
function ok(msg) { console.log(`${C.g}OK${C.n} ${msg}`); }
|
|
16
|
+
function fail(msg) { console.error(`${C.r}FAIL${C.n} ${msg}`); }
|
|
17
|
+
|
|
18
|
+
function isWin() { return process.platform === 'win32'; }
|
|
19
|
+
function npmCmd() { return isWin() ? 'npm.cmd' : 'npm'; }
|
|
20
|
+
|
|
21
|
+
function rmrf(p) {
|
|
22
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function run(cmd, args, opts = {}) {
|
|
26
|
+
const res = cp.spawnSync(cmd, args, {
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
maxBuffer: 200 * 1024 * 1024,
|
|
29
|
+
...opts,
|
|
30
|
+
});
|
|
31
|
+
return res;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runChecked(cmd, args, opts = {}) {
|
|
35
|
+
// Print the command in a dim style
|
|
36
|
+
console.log(`${C.dim}$ ${cmd} ${args.join(' ')}${C.n}`);
|
|
37
|
+
const res = run(cmd, args, opts);
|
|
38
|
+
if (res.error) throw res.error;
|
|
39
|
+
if (res.status !== 0) {
|
|
40
|
+
const err = new Error(`Command failed (${cmd} ${args.join(' ')}), exit ${res.status}`);
|
|
41
|
+
err.code = res.status;
|
|
42
|
+
err.stdout = res.stdout;
|
|
43
|
+
err.stderr = res.stderr;
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
return res;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function packTarball(root) {
|
|
50
|
+
// `npm pack --silent` prints the filename (usually one line)
|
|
51
|
+
const res = runChecked(npmCmd(), ['pack', '--silent'], { cwd: root });
|
|
52
|
+
const out = String(res.stdout || '').trim().split(/\r?\n/).filter(Boolean);
|
|
53
|
+
if (out.length === 0) throw new Error('npm pack produced no output');
|
|
54
|
+
return out[out.length - 1].trim(); // tarball filename in root
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function main() {
|
|
58
|
+
const suiteStart = Date.now();
|
|
59
|
+
const root = path.resolve(__dirname, '..');
|
|
60
|
+
|
|
61
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-smoke-'));
|
|
62
|
+
const cleanup = () => rmrf(tmp);
|
|
63
|
+
|
|
64
|
+
let tgzInRoot = null;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
info('Building tarball (npm pack)');
|
|
68
|
+
tgzInRoot = packTarball(root);
|
|
69
|
+
const srcTgz = path.join(root, tgzInRoot);
|
|
70
|
+
const dstTgz = path.join(tmp, tgzInRoot);
|
|
71
|
+
|
|
72
|
+
fs.renameSync(srcTgz, dstTgz);
|
|
73
|
+
|
|
74
|
+
info('Creating temp project + installing tarball');
|
|
75
|
+
runChecked(npmCmd(), ['init', '-y'], { cwd: tmp, stdio: 'ignore' });
|
|
76
|
+
runChecked(npmCmd(), ['install', `./${tgzInRoot}`, '--no-audit', '--no-fund'], { cwd: tmp, stdio: 'inherit' });
|
|
77
|
+
|
|
78
|
+
info('API smoke test');
|
|
79
|
+
// Run a tiny API check via node -e
|
|
80
|
+
const apiCode = `
|
|
81
|
+
const { reason } = require('eyeling');
|
|
82
|
+
const input = \`
|
|
83
|
+
{ <http://example.org/s> <http://example.org/p> <http://example.org/o>. }
|
|
84
|
+
=> { <http://example.org/s> <http://example.org/q> <http://example.org/o>. }.
|
|
85
|
+
|
|
86
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o>.
|
|
87
|
+
\`;
|
|
88
|
+
const out = reason({ proofComments: false }, input);
|
|
89
|
+
const re = /<http:\\/\\/example\\.org\\/s>\\s+<http:\\/\\/example\\.org\\/q>\\s+<http:\\/\\/example\\.org\\/o>\\s*\\./;
|
|
90
|
+
if (!re.test(out)) {
|
|
91
|
+
console.error('Unexpected output:\\n' + out);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
console.log('OK: API works');
|
|
95
|
+
`;
|
|
96
|
+
runChecked(process.execPath, ['-e', apiCode], { cwd: tmp, stdio: 'inherit' });
|
|
97
|
+
ok('API works');
|
|
98
|
+
|
|
99
|
+
info('CLI smoke test');
|
|
100
|
+
const bin = isWin()
|
|
101
|
+
? path.join(tmp, 'node_modules', '.bin', 'eyeling.cmd')
|
|
102
|
+
: path.join(tmp, 'node_modules', '.bin', 'eyeling');
|
|
103
|
+
runChecked(bin, ['-v'], { cwd: tmp, stdio: 'inherit' });
|
|
104
|
+
ok('CLI works');
|
|
105
|
+
|
|
106
|
+
info('Examples test (installed package)');
|
|
107
|
+
const examplesRunner = path.join(tmp, 'node_modules', 'eyeling', 'test', 'examples.test.js');
|
|
108
|
+
runChecked(process.execPath, [examplesRunner], { cwd: tmp, stdio: 'inherit' });
|
|
109
|
+
ok('Installed examples test passed');
|
|
110
|
+
|
|
111
|
+
const suiteMs = Date.now() - suiteStart;
|
|
112
|
+
console.log('');
|
|
113
|
+
ok(`Packaged install smoke test passed ${C.dim}(${suiteMs} ms, ${(suiteMs / 1000).toFixed(2)} s)${C.n}`);
|
|
114
|
+
process.exit(0);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.log('');
|
|
117
|
+
fail(e && e.stack ? e.stack : String(e));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
} finally {
|
|
120
|
+
// If rename failed and the tarball still exists in root, try to delete it
|
|
121
|
+
if (tgzInRoot) {
|
|
122
|
+
const maybe = path.join(root, tgzInRoot);
|
|
123
|
+
if (fs.existsSync(maybe)) {
|
|
124
|
+
try { fs.unlinkSync(maybe); } catch {}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
cleanup();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main();
|
|
132
|
+
|
package/test/packlist.test.js
CHANGED
package/examples/test
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
RED="\e[31m"
|
|
5
|
-
GREEN="\e[32m"
|
|
6
|
-
YELLOW="\e[33m"
|
|
7
|
-
NORMAL="\e[0;39m"
|
|
8
|
-
|
|
9
|
-
OK=0
|
|
10
|
-
DIFF=0
|
|
11
|
-
|
|
12
|
-
pad() {
|
|
13
|
-
# pad "text" width
|
|
14
|
-
local s="$1" w="$2"
|
|
15
|
-
printf "%-${w}s" "$s"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
now_ms() {
|
|
19
|
-
echo $(( $(date +%s%N) / 1000000 ))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
in_git_worktree() {
|
|
23
|
-
command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
# Expectation logic:
|
|
27
|
-
# 1) If the .n3 contains a comment like: # expect-exit: 2 -> use that
|
|
28
|
-
# 2) Else, if it contains "=> false" -> expect exit 2
|
|
29
|
-
# 3) Else -> expect exit 0
|
|
30
|
-
expected_exit_code() {
|
|
31
|
-
local file="$1"
|
|
32
|
-
local line
|
|
33
|
-
if line=$(grep -Em1 '^[[:space:]]*#[: ]*expect-exit:[[:space:]]*[0-9]+' "$file" 2>/dev/null); then
|
|
34
|
-
if [[ "$line" =~ ([0-9]+) ]]; then
|
|
35
|
-
echo "${BASH_REMATCH[1]}"
|
|
36
|
-
return
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
if grep -Eq '=>[[:space:]]*false\b' "$file"; then
|
|
40
|
-
echo 2
|
|
41
|
-
else
|
|
42
|
-
echo 0
|
|
43
|
-
fi
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
echo -e "${YELLOW}-------------------------------------------------${NORMAL}"
|
|
47
|
-
echo -e "${YELLOW}running eyeling examples${NORMAL}"
|
|
48
|
-
echo -e "${YELLOW}using $(../eyeling.js -v) and node $(node --version)${NORMAL}"
|
|
49
|
-
echo -e "${YELLOW}-------------------------------------------------${NORMAL}"
|
|
50
|
-
echo ""
|
|
51
|
-
|
|
52
|
-
IN_GIT=0
|
|
53
|
-
if in_git_worktree; then IN_GIT=1; fi
|
|
54
|
-
|
|
55
|
-
# In maintainer mode we write expected outputs (tracked) to output/
|
|
56
|
-
if [[ $IN_GIT -eq 1 ]]; then
|
|
57
|
-
mkdir -p output
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
begin="$(now_ms)"
|
|
61
|
-
|
|
62
|
-
for file in *.n3; do
|
|
63
|
-
printf "%s" "$(pad "$file" 36)"
|
|
64
|
-
start="$(now_ms)"
|
|
65
|
-
|
|
66
|
-
expected_rc="$(expected_exit_code "$file")"
|
|
67
|
-
|
|
68
|
-
# Decide where we write generated output
|
|
69
|
-
tmp=""
|
|
70
|
-
generated=""
|
|
71
|
-
expected="output/${file}"
|
|
72
|
-
|
|
73
|
-
if [[ $IN_GIT -eq 1 ]]; then
|
|
74
|
-
generated="$expected" # overwrite expected output in working tree
|
|
75
|
-
else
|
|
76
|
-
# npm-installed / no .git: never modify output/ in node_modules
|
|
77
|
-
if [[ ! -f "$expected" ]]; then
|
|
78
|
-
end="$(now_ms)"
|
|
79
|
-
ms=$((end-start))
|
|
80
|
-
echo -en "${YELLOW}$(pad "${ms} ms" 10)${NORMAL} "
|
|
81
|
-
echo -e "${RED}MISSING expected ${expected}${NORMAL}"
|
|
82
|
-
((++DIFF))
|
|
83
|
-
continue
|
|
84
|
-
fi
|
|
85
|
-
tmp="$(mktemp)"
|
|
86
|
-
generated="$tmp"
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# Run eyeling, capture exit code without breaking the loop
|
|
90
|
-
rc=0
|
|
91
|
-
if ../eyeling.js "$file" > "$generated"; then
|
|
92
|
-
rc=0
|
|
93
|
-
else
|
|
94
|
-
rc=$?
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
end="$(now_ms)"
|
|
98
|
-
ms=$((end-start))
|
|
99
|
-
echo -en "${YELLOW}$(pad "${ms} ms" 10)${NORMAL} "
|
|
100
|
-
|
|
101
|
-
# Compare output
|
|
102
|
-
diff_ok=0
|
|
103
|
-
if [[ $IN_GIT -eq 1 ]]; then
|
|
104
|
-
if git diff --quiet -- "$expected"; then diff_ok=1; fi
|
|
105
|
-
else
|
|
106
|
-
if command -v git >/dev/null 2>&1; then
|
|
107
|
-
if git diff --no-index --quiet -- "$expected" "$generated" >/dev/null 2>&1; then diff_ok=1; fi
|
|
108
|
-
else
|
|
109
|
-
if diff -u "$expected" "$generated" >/dev/null 2>&1; then diff_ok=1; fi
|
|
110
|
-
fi
|
|
111
|
-
fi
|
|
112
|
-
|
|
113
|
-
# Decide pass/fail
|
|
114
|
-
if [[ $diff_ok -eq 1 && $rc -eq $expected_rc ]]; then
|
|
115
|
-
if [[ $rc -eq 0 ]]; then
|
|
116
|
-
echo -e "${GREEN}OK${NORMAL}"
|
|
117
|
-
else
|
|
118
|
-
echo -e "${GREEN}OK${NORMAL} (exit ${rc})"
|
|
119
|
-
fi
|
|
120
|
-
((++OK))
|
|
121
|
-
else
|
|
122
|
-
if [[ $rc -ne $expected_rc ]]; then
|
|
123
|
-
echo -e "${RED}DIFF${NORMAL} (exit ${rc}, expected ${expected_rc})"
|
|
124
|
-
else
|
|
125
|
-
echo -e "${RED}DIFF${NORMAL}"
|
|
126
|
-
fi
|
|
127
|
-
((++DIFF))
|
|
128
|
-
|
|
129
|
-
# In npm mode, show a git-style diff when available (nice UX)
|
|
130
|
-
if [[ $IN_GIT -eq 0 ]]; then
|
|
131
|
-
if command -v git >/dev/null 2>&1; then
|
|
132
|
-
git diff --no-index -- "$expected" "$generated" || true
|
|
133
|
-
else
|
|
134
|
-
diff -u "$expected" "$generated" || true
|
|
135
|
-
fi
|
|
136
|
-
fi
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
# cleanup tmp file
|
|
140
|
-
if [[ -n "$tmp" ]]; then rm -f "$tmp"; fi
|
|
141
|
-
done
|
|
142
|
-
|
|
143
|
-
end="$(now_ms)"
|
|
144
|
-
total=$((end-begin))
|
|
145
|
-
|
|
146
|
-
echo ""
|
|
147
|
-
echo -e "${YELLOW}${total} ms${NORMAL} ${GREEN}${OK} OK${NORMAL} ${RED}${DIFF} DIFF${NORMAL}"
|
|
148
|
-
|
|
149
|
-
if [[ ${DIFF} -eq 0 ]]; then
|
|
150
|
-
exit 0
|
|
151
|
-
else
|
|
152
|
-
exit 2
|
|
153
|
-
fi
|
|
154
|
-
|
package/test/package-smoke.sh
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
if [ -t 1 ]; then
|
|
5
|
-
RED=$'\e[31m'; GREEN=$'\e[32m'; YELLOW=$'\e[33m'; NORMAL=$'\e[0m'
|
|
6
|
-
else
|
|
7
|
-
RED=""; GREEN=""; YELLOW=""; NORMAL=""
|
|
8
|
-
fi
|
|
9
|
-
|
|
10
|
-
say() { echo -e "${YELLOW}== $* ==${NORMAL}"; }
|
|
11
|
-
ok() { echo -e "${GREEN}OK${NORMAL} $*"; }
|
|
12
|
-
|
|
13
|
-
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
14
|
-
TMP="$(mktemp -d)"
|
|
15
|
-
cleanup() { rm -rf "$TMP"; }
|
|
16
|
-
trap cleanup EXIT
|
|
17
|
-
|
|
18
|
-
cd "$ROOT"
|
|
19
|
-
|
|
20
|
-
say "Building tarball"
|
|
21
|
-
TGZ="$(npm pack --silent)"
|
|
22
|
-
mv "$TGZ" "$TMP/"
|
|
23
|
-
cd "$TMP"
|
|
24
|
-
|
|
25
|
-
say "Installing tarball into temp project"
|
|
26
|
-
npm init -y >/dev/null 2>&1
|
|
27
|
-
npm install "./$TGZ" >/dev/null 2>&1
|
|
28
|
-
|
|
29
|
-
say "API smoke test"
|
|
30
|
-
node - <<'NODE'
|
|
31
|
-
const { reason } = require('eyeling');
|
|
32
|
-
const input = `
|
|
33
|
-
{ <http://example.org/s> <http://example.org/p> <http://example.org/o>. }
|
|
34
|
-
=> { <http://example.org/s> <http://example.org/q> <http://example.org/o>. }.
|
|
35
|
-
|
|
36
|
-
<http://example.org/s> <http://example.org/p> <http://example.org/o>.
|
|
37
|
-
`;
|
|
38
|
-
const out = reason({ proofComments: false }, input);
|
|
39
|
-
if (!/<http:\/\/example\.org\/s>\s+<http:\/\/example\.org\/q>\s+<http:\/\/example\.org\/o>\s*\./.test(out)) {
|
|
40
|
-
console.error("Unexpected output:\n" + out);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
NODE
|
|
44
|
-
ok "API works"
|
|
45
|
-
|
|
46
|
-
say "CLI smoke test"
|
|
47
|
-
./node_modules/.bin/eyeling -v
|
|
48
|
-
ok "CLI works"
|
|
49
|
-
|
|
50
|
-
say "Examples test (installed package)"
|
|
51
|
-
cd node_modules/eyeling/examples
|
|
52
|
-
./test
|
|
53
|
-
|
|
54
|
-
ok "packaged install smoke test passed"
|
|
55
|
-
|