Skip to content

Commit

Permalink
Add an on-disk cache (#63)
Browse files Browse the repository at this point in the history
* take a crack at a cache

* write cache key to cache file

* remove identifiers from cache key

* Normalize paths across runs

* cleanup

* Record failures in cache

* modulo more whitespace

* Fix reporting issue with replacements, add --nocache flag

* Improve error reporting for replacements

* normalize union order for type comparisons

* just normalize the type part of the quickinfo

* todo

* update test
  • Loading branch information
danvk committed Aug 3, 2023
1 parent 10b1e52 commit 1774a2f
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 39 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ module.exports = {
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-use-before-define': 'off',
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"chalk": "^2.4.2",
"fast-json-stable-stringify": "^2.1.0",
"fs-extra": "^8.1.0",
"glob": "^7.1.4",
"lodash": "^4.17.15",
Expand Down
19 changes: 13 additions & 6 deletions src/code-sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {fail} from './test-tracker';
import {extractAsciidocSamples} from './asciidoc';
import {extractMarkdownSamples} from './markdown';
import {generateIdMetadata} from './metadata';
import {dedent} from './utils';

Check warning on line 9 in src/code-sample.ts

View workflow job for this annotation

GitHub Actions / build

'dedent' is defined but never used

export interface Processor {
setLineNum(line: number): void;
Expand Down Expand Up @@ -167,12 +168,18 @@ export function checkSource(sample: CodeSample, source: string) {
// Strip out code behind HIDE..END markers
const strippedSource = stripSource(source);
if (sample.content.trim() !== strippedSource.trim()) {
fail('Inline sample does not match sample in source file', sample);
log('Inline sample:');
log(sample.content.trim());
log('----');
log('Stripped source file sample:');
log(strippedSource.trim() + '\n');
fail(
`
Inline sample for ${sample.id} does not match sample in source file
Inline sample:
${sample.content.trim()}
----
Stripped source file sample:
${strippedSource.trim()}
----
`.trimStart(),
sample,
);
return false;
}
return true;
Expand Down
30 changes: 16 additions & 14 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,8 @@ import {
} from './test-tracker';
import {checkTs, ConfigBundle} from './ts-checker';
import {CodeSample} from './types';
import {getTempDir, writeTempFile, fileSlug} from './utils';

const packagePath = path.join(
__dirname,
// The path to package.json is slightly different when you run via ts-node
__dirname.includes('dist') ? '../../package.json' : '../package.json',
);
import {writeTempFile, fileSlug} from './utils';
import {VERSION} from './version';

const argv = yargs
.strict()
Expand All @@ -49,13 +44,14 @@ const argv = yargs
type: 'boolean',
description: 'Log to stderr in addition to a log file',
},
nocache: {
type: 'boolean',
description: `Don't read previous results from cache.`,
},
})
.version(
'version',
[
`literate-ts version: ${require(packagePath).version}`,
`TypeScript version: ${ts.version}`,
].join('\n'),
[`literate-ts version: ${VERSION}`, `TypeScript version: ${ts.version}`].join('\n'),
)
.parse();

Expand Down Expand Up @@ -93,7 +89,7 @@ function checkOutput(expectedOutput: string, input: CodeSample) {
}

// Remove stack traces from output
const tmpDir = getTempDir();
const tmpDir = path.dirname(actualOutput.path);
const checkOutput = (actualOutput.stderr + actualOutput.stdout)
.split('\n')
.filter(line => !line.startsWith(' at ')) // prune stack traces to one line
Expand All @@ -117,6 +113,8 @@ function checkOutput(expectedOutput: string, input: CodeSample) {
log('Actual:');
log(checkOutput);
log('----');
} else {
log('Actual output matched expected.');
}
}

Expand All @@ -126,7 +124,12 @@ async function checkSample(sample: CodeSample, idToSample: {[id: string]: CodeSa
startSample(sample);

if (language === 'ts' || (language === 'js' && sample.checkJS)) {
await checkTs(sample, id + '-output' in idToSample, typeScriptBundle);
const result = await checkTs(sample, id + '-output' in idToSample, typeScriptBundle, {
skipCache: !!argv.nocache,
});
if (result.output !== undefined) {
sample.output = result.output;
}
} else if (language === 'js') {
// Run the sample through Node and record the output.
const path = writeTempFile(id + '.js', content);
Expand All @@ -147,7 +150,6 @@ async function checkSample(sample: CodeSample, idToSample: {[id: string]: CodeSa
fail(`No paired input: #${inputId}`);
} else {
checkOutput(content, input);
log('Actual output matched expected.');
}
}
finishSample();
Expand Down
5 changes: 3 additions & 2 deletions src/node-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ export interface ExecErrorType {
code: number;
stdout: string;
stderr: string;
path: string;
}

/** Run a JavaScript program through Node.js. Returns the output. */
export async function runNode(path: string): Promise<ExecErrorType> {
try {
const {stdout, stderr} = await util.promisify(exec)(`node ${path}`);
return {code: 0, stderr, stdout};
return {code: 0, stderr, stdout, path};
} catch (eIn) {
const e = eIn as ExecErrorType;
const {code, stderr, stdout} = e;
log(`Node exited with error ${e.code} on ${path}`);
return {code, stderr, stdout};
return {code, stderr, stdout, path};
}
}
11 changes: 9 additions & 2 deletions src/test-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,30 @@ export function finishSample() {
const elapsedMs = Date.now() - sampleStartMs;
log(`\nEND #${currentSample!.descriptor} (${elapsedMs} ms)\n`);
currentSample = undefined;
lastFailReason = null;
}

let lastFailReason: string | null = null;
export function fail(message: string, sample?: CodeSample) {
if (sample === undefined) {
sample = currentSample;
}
lastFailReason = message;

const fullMessage = `💥 ${currentSample?.descriptor}: ${message}`;
const fullMessage = `💥 ${sample?.descriptor}: ${message}`;
if (!isLoggingToStderr()) {
console.error('\n' + fullMessage);
}
log(fullMessage);
if (!(global as any).__TEST__) {

Check warning on line 47 in src/test-tracker.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
results[currentFile][currentSample!.descriptor]++;
results[currentFile][sample!.descriptor]++;
}
}

export function getLastFailReason(): string | null {
return lastFailReason;
}

export function getTestResults() {
return results;
}
37 changes: 37 additions & 0 deletions src/test/ts-checker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
extractTypeAssertions,
checkTypeAssertions,
getLanguageServiceHost,
matchModuloWhitespace,
sortUnions,
} from '../ts-checker';
import {dedent} from '../utils';

Expand Down Expand Up @@ -491,4 +493,39 @@ describe('ts-checker', () => {
// `)).toBe(true);
// });
});

describe('matchModuloWhitespace', () => {
it('should normalize whitespace around parens', () => {
const expected =
'type T = <T extends object, K extends keyof T>( obj: T, ...keys: K[] ) => Pick<T, K>';
const actual =
'type T = <T extends object, K extends keyof T>(obj: T, ...keys: K[]) => Pick<T, K>';
expect(matchModuloWhitespace(actual, expected)).toBe(true);
});

it('should flag different types', () => {
expect(matchModuloWhitespace('const x: string', 'const x: number')).toBe(false);
});

it('should allow differing orders for unions', () => {
expect(matchModuloWhitespace('const ab: B | A', 'const ab: A|B')).toBe(true);
});
});

describe('sortUnions', () => {
it('should sort unions', () => {
expect(sortUnions('C | B | A')).toEqual('A | B | C');
expect(sortUnions('string | Date | number')).toEqual('Date | number | string');
});

it('should ignore unions inside types', () => {
expect(sortUnions(`{x:C|B|A}`)).toEqual(`{x:C|B|A}`);
expect(sortUnions(`(x:C|B|A)=>void`)).toEqual(`(x:C|B|A)=>void`);
expect(sortUnions(`Partial<C|B|A>`)).toEqual(`Partial<C|B|A>`);
});

it('should sort unions of complex types', () => {
expect(sortUnions(`{foo:B|A} | {bar:C|D}`)).toEqual(`{bar:C|D} | {foo:B|A}`);
});
});
});
8 changes: 7 additions & 1 deletion src/test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {matchAndExtract} from '../utils';
import {matchAndExtract, sha256} from '../utils';

describe('utils', () => {
test('matchAndExtract', () => {
Expand All @@ -7,4 +7,10 @@ describe('utils', () => {
expect(matchAndExtract(pat, 'hello foo bar baz')).toEqual('bar');
expect(matchAndExtract(pat, 'foo baz')).toEqual(null);
});

test('sha256', () => {
expect(sha256('')).toMatchInlineSnapshot(
`"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"`,
);
});
});

0 comments on commit 1774a2f

Please sign in to comment.