aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/diagcache_test.js
blob: 43966cf10712312307a48517cb4fa7d09ed0db2a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// the rules of the diagnostics cache (editors/vscode/client/diagcache.js), with
// plain node and no vscode: a key covers the server build, the text, and every
// file the text reads through include, transitively.
//
//   node tests/drivers/diagcache_test.js

const fs = require('fs');
const os = require('os');
const path = require('path');
const assert = require('assert');
const dc = require(path.join(__dirname, '..', '..', 'editors', 'vscode', 'client', 'diagcache.js'));

let passed = 0, failed = 0;
function check(name, fn) {
    try { fn(); passed++; console.log('ok   ' + name); }
    catch (e) { failed++; console.log('FAIL ' + name + ': ' + e.message); }
}

const root = fs.mkdtempSync(path.join(os.tmpdir(), 'zc-diagcache-'));
const write = (rel, text) => { const p = path.join(root, rel); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, text); return p; };
const A = write('a.zc', 'include "b.zc";\nfn main() { return helper(); }\n');
const B = write('b.zc', 'include "c.zc";\n');
const C = write('c.zc', 'fn helper() { return 1; }\n');
const U = write('unrelated.zc', 'fn other() { return 2; }\n');
const aText = fs.readFileSync(A, 'utf8');

const keyA = (stamp = 'S1', text = aText, p = A) => dc.diagKey(stamp, text, p, root).key;

check('includeNames reads every include, however it is spaced', () => {
    assert.deepStrictEqual(dc.includeNames('include "a.zc";\ninclude"b/c.zc";\nfn f() {}'), ['a.zc', 'b/c.zc']);
    assert.deepStrictEqual(dc.includeNames('fn f() { return 1; }'), []);
});

check('includeNames ignores comments and literals while allowing comments between tokens', () => {
    assert.deepStrictEqual(dc.includeNames('// include "fake.zc"\n/* include "also-fake.zc" */\nlet s = "include \\"string.zc\\"";\ninclude /* dependency */ "real.zc";'), ['real.zc']);
});

check('the same tree gives the same key', () => {
    assert.strictEqual(keyA(), keyA());
    assert.match(keyA(), /^[0-9a-f]{64}$/);
});

check('a change to the document text changes the key', () => {
    assert.notStrictEqual(keyA('S1', aText + ' '), keyA());
});

check('a change to the server build changes the key', () => {
    assert.notStrictEqual(keyA('S2'), keyA('S1'));
});

check('a change to a file the document includes, two steps down, changes the key', () => {
    const before = keyA();
    fs.writeFileSync(C, 'fn helper() { return 3; }\n');
    const after = keyA();
    assert.notStrictEqual(before, after);
    fs.writeFileSync(C, 'fn helper() { return 1; }\n');
    assert.strictEqual(keyA(), before, 'putting it back gives the old key again');
});

check('a file nothing includes does not change the key', () => {
    const before = keyA();
    fs.writeFileSync(U, 'fn other() { return 9; }\n');
    assert.strictEqual(keyA(), before);
});

check('creating a missing include changes the key', () => {
    const M = write('m.zc', 'include "later.zc";\nfn f() { return 0; }\n');
    const mText = fs.readFileSync(M, 'utf8');
    const before = dc.diagKey('S1', mText, M, root);
    assert.ok(before.deps.some(p => p.endsWith('later.zc')), 'the missing name is still a dependency');
    write('later.zc', 'fn g() { return 1; }\n');
    assert.notStrictEqual(dc.diagKey('S1', mText, M, root).key, before.key);
});

check('an include is found from the workspace folder as well as the including file', () => {
    const X = write('sub/x.zc', 'include "b.zc";\nfn x() { return 0; }\n');
    const tree = dc.sourceTree(fs.readFileSync(X, 'utf8'), X, root);
    assert.ok(tree.files.has(path.join(root, 'b.zc')), 'b.zc is read from the workspace root');
    assert.ok(tree.files.has(path.join(root, 'sub', 'b.zc')), 'and the directory of x.zc is tried too');
    assert.ok(tree.complete);
});

check('a tree too large to cover has no key, so nothing is cached for it', () => {
    let many = '';
    for (let i = 0; i < dc.MAX_FILES + 5; i++) many += 'include "nope' + i + '.zc";\n';
    const big = write('big.zc', many);
    const r = dc.diagKey('S1', many, big, root);
    assert.strictEqual(r.key, null);
    const cache = new dc.DiagCache(null);
    assert.strictEqual(cache.put(r.key, []), false);
    assert.strictEqual(cache.size(), 0);
});

check('the cache answers only for the key it was given', () => {
    const cache = new dc.DiagCache(null);
    const d = [{ message: 'x' }];
    assert.ok(cache.put('k1', d));
    assert.deepStrictEqual(cache.get('k1'), d);
    assert.strictEqual(cache.get('k2'), null);
    assert.strictEqual(cache.get(null), null);
    assert.strictEqual(cache.put(null, []), false, 'a tree without a key is not kept');
    assert.strictEqual(cache.size(), 1);
});

check('two states of one document are both kept, so going back is a hit', () => {
    const cache = new dc.DiagCache(null);
    cache.put('state-a', []);
    cache.put('state-b', [{ message: 'b' }]);
    assert.deepStrictEqual(cache.get('state-a'), []);
    assert.deepStrictEqual(cache.get('state-b'), [{ message: 'b' }]);
});

check('the least recently used entries go first past the limit', () => {
    const cache = new dc.DiagCache(null, 2);
    cache.put('1', []);
    cache.put('2', []);
    cache.get('1');
    cache.put('3', []);
    assert.strictEqual(cache.size(), 2);
    assert.strictEqual(cache.get('2'), null, 'two was the least recently used');
    assert.deepStrictEqual(cache.get('1'), []);
    assert.deepStrictEqual(cache.get('3'), []);
});

check('the cache carries over a save and load as plain JSON', () => {
    const cache = new dc.DiagCache(null);
    cache.put('k1', [{ message: 'z', range: { start: { line: 1, character: 2 } } }]);
    const back = new dc.DiagCache(JSON.parse(JSON.stringify(cache)));
    assert.deepStrictEqual(back.get('k1'), [{ message: 'z', range: { start: { line: 1, character: 2 } } }]);
});

check('a saved entry that is not well formed is dropped, not trusted', () => {
    const back = new dc.DiagCache({ bad: { key: 'bad' }, good: [] });
    assert.strictEqual(back.get('bad'), null);
    assert.deepStrictEqual(back.get('good'), []);
});

fs.rmSync(root, { recursive: true, force: true });
console.log(passed + ' passed, ' + failed + ' failed');
process.exit(failed ? 1 : 0);