Skip to content

Commit

Permalink
test: add fixture mechanism for unit testing (#38)
Browse files Browse the repository at this point in the history
Makes it possible to run runs with fixtures without having out of band
fixtures directories.
  • Loading branch information
ofrobots committed Aug 14, 2017
1 parent 0c0f9cd commit c692d06
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 18 deletions.
27 changes: 23 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"lint": "tslint -c tslint.json --project . --type-check -t codeFrame",
"lint-fix": "tslint -c tslint.json --project . --type-check -t codeFrame --fix",
"release": "standard-version",
"test": "npm run build && ava build/test/",
"test": "npm run compile && ava build/test/test*.js",
"posttest": "npm run lint && npm run format-check"
},
"engines": {
Expand All @@ -53,24 +53,28 @@
"@types/chalk": "^0.4.31",
"@types/glob": "^5.0.30",
"@types/inquirer": "0.0.35",
"@types/make-dir": "^1.0.1",
"@types/meow": "^3.6.2",
"@types/ncp": "^2.0.0",
"@types/node": "^8.0.17",
"@types/pify": "0.0.28",
"@types/rimraf": "2.0.0",
"@types/tmp": "0.0.33",
"@types/update-notifier": "^1.0.1",
"ava": "^0.21.0",
"make-dir": "^1.0.0",
"ncp": "^2.0.0",
"source-map-support": "^0.4.15",
"standard-version": "^4.2.0",
"tmp": "0.0.31",
"typescript": "^2.4.1"
},
"peerDependencies": {
"typescript": "^2.4.1"
},
"ava": {
"require": [
"source-map-support"
"source-map-support/register"
]
}
}
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ switch (verb) {
init(options);
break;
case 'lint':
lint(false, options);
lint(options);
break;
case 'fix':
fix(options);
Expand Down
2 changes: 1 addition & 1 deletion src/fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ import {lint} from './lint';
* Run tslint fix and clang fix with the default configuration
*/
export async function fix(options: Options): Promise<boolean> {
return lint(true, options) && await format(true, options);
return lint(options, true /*fix*/) && await format(true, options);
}
8 changes: 6 additions & 2 deletions src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,14 @@ async function generateTsConfig(options: Options): Promise<void> {
}
}

const pkgDir = path.relative(options.targetRootDir, options.gtsRootDir);
// typescript expects relative paths to start with './'.
const baseConfig = './' +
path.relative(
options.targetRootDir,
path.resolve(options.gtsRootDir, 'tsconfig-google.json'));
const tsconfig = JSON.stringify(
{
extends: `${path.join(pkgDir, 'tsconfig-google.json')}`,
extends: baseConfig,
compilerOptions: {rootDir: '.', outDir: 'build'},
include: ['src/*.ts', 'src/**/*.ts', 'test/*.ts', 'test/**/*.ts'],
exclude: ['node_modules']
Expand Down
7 changes: 4 additions & 3 deletions src/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,23 @@ import {Options} from './cli';
* @param fix automatically fix linter errors
* @param options google-ts-style options
*/
export function lint(fix: boolean, options: Options): boolean {
export function lint(options: Options, fix = false, silent = false): boolean {
const tslintConfigPath = path.join(options.gtsRootDir, 'tslint.json');

const program = createProgram(options);
const configuration =
Configuration.findConfiguration(tslintConfigPath, '').results;
const linter = new Linter({fix: fix, formatter: 'codeFrame'}, program);
const files = Linter.getFileNames(program);
console.log(files);
files.forEach(file => {
const fileContents = program.getSourceFile(file).getFullText();
linter.lint(file, fileContents, configuration);
});
const result = linter.getResult();
if (result.errorCount || result.warningCount) {
console.log(result.output);
if (!silent) {
console.log(result.output);
}
return false;
}
return true;
Expand Down
79 changes: 79 additions & 0 deletions test/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {test, Test} from 'ava';
import * as fs from 'fs';
import * as makeDir from 'make-dir';
import * as path from 'path';
import * as pify from 'pify';
import * as tmp from 'tmp';

const writeFilep = pify(fs.writeFile);

export interface Fixtures {
// If string, we create a file with that string contents. If fixture, we
// create a subdirectory and recursively install the fixture.
// TODO: support buffers to allow non-text files.
[name: string]: string|Fixtures;
}

async function setupFixtures(dir: string, fixtures: Fixtures) {
await makeDir(dir);
const keys = Object.keys(fixtures);
for (let key of keys) {
const filePath = path.join(dir, key);
if (typeof fixtures[key] === 'string') {
const contents = fixtures[key] as string;
await writeFilep(filePath, contents);
} else {
const fixture = fixtures[key] as Fixtures;
await setupFixtures(filePath, fixture);
}
}
}

export async function withFixtures(
fixtures: Fixtures, fn: (fixturesDir: string) => Promise<any>) {
const keep = !!process.env.GTS_KEEP_TEMPDIRS;
const dir = tmp.dirSync({keep: keep, unsafeCleanup: true});

await setupFixtures(dir.name, fixtures);

const origDir = process.cwd();
process.chdir(dir.name);

const result = await fn(dir.name);

process.chdir(origDir);
if (!keep) {
dir.removeCallback();
}

return result;
}

// Problem: the following doesn't quite work with Ava. Ava expects direct calls
// to `test`, and if it doesn't find them, it thinks that there do not exist
// any tests in a file.
// TODO: figure out a solution. Without this, the users of withFixtures have to
// remember to use test.serial rather than test.
export async function testWithFixtures(
name: string, fixtures: Fixtures, testFn: Test) {
return await withFixtures(fixtures, async () => {
// The tests are run serially because we chdir to temp directory.
return await test.serial(name, testFn);
});
}
2 changes: 1 addition & 1 deletion test/fixtures/kitchen/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "./tsconfig-google.json",
"extends": "./node_modules/google-ts-style/tsconfig-google.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build"
Expand Down
146 changes: 146 additions & 0 deletions test/test-lint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import test from 'ava';

import {Options} from '../src/cli';
import * as lint from '../src/lint';

import {withFixtures} from './fixtures';

const OPTIONS: Options = {
gtsRootDir: './',
targetRootDir: './',
dryRun: false,
yes: false
};

const GOOD_CODE = `
console.log('hello world');
`;

const BAD_CODE = `console.log('hello world');`; // no newline.

const TSLINT_CONFIG = require('../../tslint.json');

test.serial('createProgram should return an object', async t => {
await withFixtures({'tsconfig.json': '{}'}, async () => {
const program = lint.createProgram(OPTIONS);
t.truthy(program);
});
});

// TODO: make the error friendlier. This error implies that our module messed
// up, or is not installed/integrated properly.
test.serial('lint should throw error if tslint is missing', async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({files: ['a.ts']}),
'a.ts': GOOD_CODE,
},
async () => {
const err = t.throws(() => {
lint.lint(OPTIONS);
});
t.truthy(err.message.match('Could not find config.*tslint\.json'));
});
});

test.serial('lint should return true on good code', async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({files: ['a.ts']}),
'tslint.json': JSON.stringify(TSLINT_CONFIG),
'a.ts': GOOD_CODE,
},
async () => {
const okay = lint.lint(OPTIONS);
t.is(okay, true);
});
});

test.serial('lint should return false on bad code', async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({files: ['a.ts']}),
'tslint.json': JSON.stringify(TSLINT_CONFIG),
'a.ts': BAD_CODE,
},
async () => {
const okay = lint.lint(OPTIONS, false, true /*silent*/);
t.is(okay, false);
});
});

test.serial('lint should lint files listed in tsconfig.files', async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({files: ['a.ts']}),
'tslint.json': JSON.stringify(TSLINT_CONFIG),
'a.ts': GOOD_CODE,
'b.ts': BAD_CODE
},
async () => {
const okay = lint.lint(OPTIONS);
t.is(okay, true);
});
});

test.serial(
'lint should lint *.ts files when no files or inlcude has been specified',
async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({}),
'tslint.json': JSON.stringify(TSLINT_CONFIG),
'a.ts': GOOD_CODE,
'b.ts': BAD_CODE
},
async () => {
const okay = lint.lint(OPTIONS, false, true /*silent*/);
t.is(okay, false);
});
});

test.serial('lint should not lint files listed in exclude', async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({exclude: ['b.*']}),
'tslint.json': JSON.stringify(TSLINT_CONFIG),
'a.ts': GOOD_CODE,
'b.ts': BAD_CODE
},
async () => {
const okay = lint.lint(OPTIONS);
t.is(okay, true);
});
});

test.serial('lint should lint globs listed in include', async t => {
await withFixtures(
{
'tsconfig.json': JSON.stringify({include: ['dirb/*']}),
'tslint.json': JSON.stringify(TSLINT_CONFIG),
dira: {'a.ts': GOOD_CODE},
dirb: {'b.ts': BAD_CODE}
},
async () => {
const okay = lint.lint(OPTIONS, false, true /*silent*/);
t.is(okay, false);
});
});

// TODO: test for when tsconfig.json is missing.
4 changes: 1 addition & 3 deletions test/test-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import {getTSConfig} from '../src/util';

test('get should parse the correct tsconfig file', async t => {
const FAKE_DIRECTORY = '/some/fake/directory';
const FAKE_CONFIG = {
a: 'b'
};
const FAKE_CONFIG = {a: 'b'};
function fakeReadFilep(configPath: string, encoding: string): Promise<any> {
t.is(configPath, path.join(FAKE_DIRECTORY, 'tsconfig.json'));
t.is(encoding, 'utf8');
Expand Down
Loading

0 comments on commit c692d06

Please sign in to comment.