Skip to content

Commit

Permalink
fix: export testing utilities (projen#1310)
Browse files Browse the repository at this point in the history
Fixes projen#1160

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • Loading branch information
Chriscbr authored Dec 8, 2021
1 parent b208ea9 commit 898dd97
Show file tree
Hide file tree
Showing 64 changed files with 228 additions and 196 deletions.
24 changes: 24 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Name|Description
[Task](#projen-task)|A task that can be performed on the project.
[TaskRuntime](#projen-taskruntime)|The runtime component of the tasks engine.
[Tasks](#projen-tasks)|Defines project tasks.
[Testing](#projen-testing)|A Testing static class with a .synth helper for getting a snapshots of construct outputs. Useful for snapshot testing with Jest.
[TextFile](#projen-textfile)|A text file.
[TomlFile](#projen-tomlfile)|Represents a TOML file.
[Version](#projen-version)|*No description*
Expand Down Expand Up @@ -2579,6 +2580,29 @@ __Returns__:



## class Testing 🔹 <a id="projen-testing"></a>

A Testing static class with a .synth helper for getting a snapshots of construct outputs. Useful for snapshot testing with Jest.


### Methods


#### *static* synth(project)🔹 <a id="projen-testing-synth"></a>

Produces a simple JS object that represents the contents of the projects with field names being file paths.

```ts
static synth(project: Project): Map<string, any>
```

* **project** (<code>[Project](#projen-project)</code>) the project to produce a snapshot for.

__Returns__:
* <code>Map<string, any></code>



## class TextFile 🔹 <a id="projen-textfile"></a>

A text file.
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './task';
export * from './tasks';
export * from './task-model';
export * from './task-runtime';
export * from './testing';
export * from './textfile';
export * from './toml';
export * from './version';
Expand Down
138 changes: 2 additions & 136 deletions src/util/synth.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,18 @@
import * as cp from 'child_process';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs-extra';
import { glob } from 'glob';
import { Project } from '../';
import { GitHubProject, GitHubProjectOptions } from '../github';
import * as logging from '../logging';
import { Task } from '../task';
import { exec } from '../util';

const PROJEN_CLI = require.resolve('../../lib/cli/index.js');

logging.disable(); // no logging during tests

export class TestProject extends GitHubProject {
constructor(options: Omit<GitHubProjectOptions, 'name'> = {}) {
super({
name: 'my-project',
clobber: false,
...options,
});
}

// override runTaskCommand in tests since the default includes the version
// number and that will break regresion tests.
public runTaskCommand(task: Task) {
return `projen ${task.name}`;
}

postSynthesize() {
fs.writeFileSync(path.join(this.outdir, '.postsynth'), '# postsynth');
}
}

export function execProjenCLI(workdir: string, args: string[] = []) {
const command = [
process.execPath,
PROJEN_CLI,
...args,
];

return exec(command.map(x => `"${x}"`).join(' '), { cwd: workdir });
}
import { Project } from '../project';

export interface SynthOutput {
[filePath: string]: any;
}

const autoRemove = new Set<string>();

// Hook to automatically remove temporary directories without needing each
// place to actually handle this specifically.
afterAll((done) => {
// Array.from used to get a copy, so we can safely remove from the set
for (const dir of Array.from(autoRemove)) {
try {
// Note - fs-extra.removeSync is idempotent, so we're safe if the
// directory has already been cleaned up before we get there!
fs.removeSync(dir);
} catch (e) {
done.fail(e);
}
autoRemove.delete(dir);
}
done();
});

export function mkdtemp() {
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'projen-test-'));
autoRemove.add(tmpdir);
return tmpdir;
}

/**
* Creates a snapshot of the files generated by a project. Ignores any non-text
* files so that the snapshots are human readable.
*/
export function synthSnapshot(project: Project): any {
export function synthSnapshot(project: Project): SynthOutput {
// defensive: verify that "outdir" is actually in a temporary directory
if (!path.resolve(project.outdir).startsWith(os.tmpdir()) && !project.outdir.includes('project-temp-dir')) {
throw new Error('Trying to capture a snapshot of a project outside of tmpdir, which implies this test might corrupt an existing project');
Expand Down Expand Up @@ -107,15 +43,6 @@ export function synthSnapshot(project: Project): any {
}
}

export function synthSnapshotWithPost(project: Project) {
try {
project.synth();
return directorySnapshot(project.outdir);
} finally {
fs.removeSync(project.outdir);
}
}

export interface DirectorySnapshotOptions {
/**
* Globs of files to exclude.
Expand Down Expand Up @@ -149,64 +76,3 @@ export function directorySnapshot(root: string, options: DirectorySnapshotOption

return output;
}

export function withProjectDir(code: (workdir: string) => void, options: { git?: boolean; chdir?: boolean } = {}) {
const origDir = process.cwd();
const outdir = mkdtemp();
try {
// create project under "my-project" so that basedir is deterministic
const projectdir = path.join(outdir, 'my-project');
fs.mkdirSync(projectdir);

const shell = (command: string) => cp.execSync(command, { cwd: projectdir });
if (options.git ?? true) {
shell('git init');
shell('git remote add origin git@boom.com:foo/bar.git');
shell('git config user.name "My User Name"');
shell('git config user.email "my@user.email.com"');
} else if (process.env.CI) {
// if "git" is set to "false", we still want to make sure global user is defined
// (relevant in CI context)
shell('git config user.name || git config --global user.name "My User Name"');
shell('git config user.email || git config --global user.email "my@user.email.com"');
}

if (options.chdir ?? false) {
process.chdir(projectdir);
}

code(projectdir);
} finally {
process.chdir(origDir);
fs.removeSync(outdir);
}
}

/**
* Removes any non-deterministic aspects from the synthesized output.
* @param dir The output directory.
*/
export function sanitizeOutput(dir: string) {
const filepath = path.join(dir, 'package.json');
const pkg = fs.readJsonSync(filepath);
const prev = pkg.devDependencies.projen;
if (!prev) {
throw new Error(`expecting "${filepath}" to include a devDependency on "projen"`);
}

// replace the current projen version with 999.999.999 for deterministic output.
// this will preserve any semantic version requirements (e.g. "^", "~", etc).
pkg.devDependencies.projen = prev.replace(/\d+\.\d+\.\d+/, '999.999.999');
fs.writeJsonSync(filepath, pkg);

// we will also patch deps.json so that all projen deps will be set to 999.999.999
const depsPath = path.join(dir, '.projen', 'deps.json');
const deps = fs.readJsonSync(depsPath);
for (const dep of deps.dependencies) {
if (dep.name === 'projen' && dep.version) {
dep.version = dep.version.replace(/\d+\.\d+\.\d+/, '999.999.999');
}
}
fs.chmodSync(depsPath, '777');
fs.writeJsonSync(depsPath, deps);
}
2 changes: 1 addition & 1 deletion test/awscdk-app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync } from 'fs';
import { join } from 'path';
import { awscdk } from '../src';
import { AwsCdkTypeScriptApp } from '../src/awscdk';
import { mkdtemp, synthSnapshot } from '../src/util/synth';
import { mkdtemp, synthSnapshot } from './util';

describe('cdkVersion is >= 2.0.0', () => {
test('use "aws-cdk-lib" the constructs at ^10.0.5', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/awscdk-construct.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { join } from 'path';
import { awscdk } from '../src';
import { AwsCdkConstructLibrary, AwsCdkConstructLibraryOptions } from '../src/awscdk';
import { NpmAccess } from '../src/javascript';
import { mkdtemp, synthSnapshot } from '../src/util/synth';
import { mkdtemp, synthSnapshot } from './util';

describe('constructs dependency selection', () => {
test('user-selected', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/cdk8s-app-project-ts.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Cdk8sTypeScriptApp } from '../src/cdk8s';
import { synthSnapshot } from '../src/util/synth';
import { synthSnapshot } from './util';

test ('test if cdk8s synth is possible', () => {
const project = new Cdk8sTypeScriptApp({
Expand Down
2 changes: 1 addition & 1 deletion test/cdk8s-construct.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ConstructLibraryCdk8s } from '../src/cdk8s';
import { synthSnapshot } from '../src/util/synth';
import { synthSnapshot } from './util';

test ('constructs version defined', () => {
const project = new ConstructLibraryCdk8s({
Expand Down
2 changes: 1 addition & 1 deletion test/cdktf-construct.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConstructLibraryCdktf, ConstructLibraryCdktfOptions } from '../src/cdktf';
import { NpmAccess } from '../src/javascript';
import { synthSnapshot } from '../src/util/synth';
import { synthSnapshot } from './util';

describe('constructs dependency selection', () => {
test('user-selected', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join } from 'path';
import { writeFileSync } from 'fs-extra';
import { Project } from '../src/project';
import { directorySnapshot, execProjenCLI, mkdtemp } from '../src/util/synth';
import { directorySnapshot, execProjenCLI, mkdtemp } from './util';

const MOCK_PROJENRC = "new (require('projen').Project)({ name: 'foo' }).synth()";

Expand Down
2 changes: 1 addition & 1 deletion test/deps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { join } from 'path';
import { existsSync, readFileSync } from 'fs-extra';
import { Project } from '../src';
import { Dependencies, DependencyType } from '../src/dependencies';
import { TestProject } from '../src/util/synth';
import { TestProject } from './util';

test('no dependencies, no manifest', () => {
// GIVEN
Expand Down
2 changes: 1 addition & 1 deletion test/dev-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { DevEnvironmentDockerImage } from '../src/dev-env';
import { FileBase } from '../src/file';
import { Gitpod, GitpodOpenIn, GitpodOpenMode } from '../src/gitpod';
import * as logging from '../src/logging';
import { synthSnapshot, TestProject } from '../src/util/synth';
import { synthSnapshot, TestProject } from './util';

// This is duplicated vs exported
const GITPOD_FILE = '.gitpod.yml';
Expand Down
2 changes: 1 addition & 1 deletion test/docker-compose.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';
import * as fs from 'fs-extra';
import { DockerCompose, DockerComposeProtocol } from '../src';
import * as logging from '../src/logging';
import { TestProject } from '../src/util/synth';
import { TestProject } from './util';

logging.disable();

Expand Down
2 changes: 1 addition & 1 deletion test/eslint.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Eslint, NodeProject } from '../src/javascript';
import { synthSnapshot } from '../src/util/synth';
import { synthSnapshot } from './util';

test('devdirs', () => {
// GIVEN
Expand Down
2 changes: 1 addition & 1 deletion test/github/auto-approve.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AutoApprove } from '../../src/github/auto-approve';
import { NodeProject, NodeProjectOptions } from '../../src/javascript';
import { synthSnapshot } from '../../src/util/synth';
import { synthSnapshot } from '../util';

describe('auto-approve', () => {
test('default', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/github/dependabot.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dependabot, DependabotRegistryType } from '../../src/github';
import { NodeProject, NodeProjectOptions } from '../../src/javascript';
import { synthSnapshot } from '../../src/util/synth';
import { synthSnapshot } from '../util';

describe('dependabot', () => {
test('default', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/github/github-workflow.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GithubWorkflow } from '../../src/github/workflows';
import { synthSnapshot, TestProject } from '../../src/util/synth';
import { synthSnapshot, TestProject } from '../util';

describe('github-workflow', () => {
const workflowName = 'test-workflow';
Expand Down
2 changes: 1 addition & 1 deletion test/github/mergify.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NodeProject, NodeProjectOptions } from '../../src/javascript';
import { synthSnapshot } from '../../src/util/synth';
import { synthSnapshot } from '../util';

describe('mergify', () => {
test('default', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/github/pull-request-lint.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PullRequestLint } from '../../src/github/pull-request-lint';
import { NodeProject, NodeProjectOptions } from '../../src/javascript';
import { synthSnapshot } from '../../src/util/synth';
import { synthSnapshot } from '../util';

test('default', () => {
// GIVEN
Expand Down
2 changes: 1 addition & 1 deletion test/github/stale.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StaleBehavior } from '../../src/github';
import { renderBehavior } from '../../src/github/stale-util';
import { synthSnapshot, TestProject } from '../../src/util/synth';
import { synthSnapshot, TestProject } from '../util';

const defaults = { stale: 10, close: 11, type: 'issue' };

Expand Down
2 changes: 1 addition & 1 deletion test/github/task-workflow.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TaskWorkflow } from '../../src/github/task-workflow';
import { Task } from '../../src/task';
import { synthSnapshot, TestProject } from '../../src/util/synth';
import { synthSnapshot, TestProject } from '../util';

describe('task-workflow', () => {
test('default', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/github/workflows.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Project } from '../../src/project';
import { synthSnapshot, TestProject } from '../../src/util/synth';
import { synthSnapshot, TestProject } from '../util';

test('no workflow', () => {
// GIVEN
Expand Down
2 changes: 1 addition & 1 deletion test/ignore-file.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IgnoreFile } from '../src';
import { synthSnapshot, TestProject } from '../src/util/synth';
import { synthSnapshot, TestProject } from './util';

test('ignorefile synthesizes correctly', () => {
// GIVEN
Expand Down
2 changes: 1 addition & 1 deletion test/ini.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as INI from 'ini';
import { IniFile } from '../src';
import { synthSnapshot, TestProject } from '../src/util/synth';
import { synthSnapshot, TestProject } from './util';

test('ini object can be mutated before synthesis', () => {
const prj = new TestProject();
Expand Down
2 changes: 1 addition & 1 deletion test/integ.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join, dirname, basename } from 'path';
import { copySync } from 'fs-extra';
import { glob } from 'glob';
import { mkdtemp, directorySnapshot, execProjenCLI, sanitizeOutput } from '../src/util/synth';
import { mkdtemp, directorySnapshot, execProjenCLI, sanitizeOutput } from './util';

const samples = join(__dirname, 'integration');
const files = glob.sync('**/*.projenrc.js', { cwd: samples });
Expand Down
2 changes: 1 addition & 1 deletion test/java/java-project.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JavaProject, JavaProjectOptions } from '../../src/java/java-project';
import { renderProjenNewOptions } from '../../src/javascript/render-options';
import { synthSnapshot } from '../../src/util/synth';
import { synthSnapshot } from '../util';

test('defaults', () => {
const p = new TestJavaProject();
Expand Down
2 changes: 1 addition & 1 deletion test/java/pom.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DependencyType } from '../../src/dependencies';
import { Pom, PomOptions } from '../../src/java';
import { synthSnapshot, TestProject } from '../../src/util/synth';
import { synthSnapshot, TestProject } from '../util';

test('group/artifact/version', () => {
const pom = new TestPom({
Expand Down
Loading

0 comments on commit 898dd97

Please sign in to comment.