Skip to content

Commit

Permalink
chore: migrate affected tests to esm (#4158)
Browse files Browse the repository at this point in the history
* chore: migrate affected tests to esm

* chore: fix prettier
  • Loading branch information
lukasholzer committed Feb 4, 2022
1 parent 3dd9f74 commit 0577c15
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 137 deletions.
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -63,7 +63,7 @@
"test:dev:ava": "ava --verbose",
"test:ci:ava:unit": "c8 -r json ava --no-worker-threads tests/unit/**/*.test.js tools/**/*.test.js",
"test:ci:ava:integration": "c8 -r json ava --concurrency 1 --no-worker-threads tests/integration/**/*.test.js",
"test:affected": "node ./tools/affected-test.js",
"test:affected": "node ./tools/affected-test.mjs",
"e2e": "node ./tools/e2e/run.mjs",
"docs": "node ./site/scripts/docs.mjs",
"watch": "c8 --reporter=lcov ava --watch",
Expand All @@ -73,7 +73,7 @@
"postinstall": "node ./scripts/postinstall.js"
},
"config": {
"eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"",
"eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"",
"prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!.github/**/*.md\""
},
"dependencies": {
Expand Down Expand Up @@ -215,7 +215,7 @@
},
"ava": {
"files": [
"tools/**/*.test.js",
"tools/**/*.test.mjs",
"tests/**/*.test.js"
],
"cache": true,
Expand Down
32 changes: 17 additions & 15 deletions tools/affected-test.js → tools/affected-test.mjs
@@ -1,16 +1,20 @@
#!/usr/bin/env node
// @ts-check
const { existsSync, statSync } = require('fs')
const { join } = require('path')
const process = require('process')
import { existsSync, readFileSync, statSync } from 'fs'
import { join } from 'path'
import process from 'process'
import { fileURLToPath } from 'url'

const { grey } = require('chalk')
const execa = require('execa')
const { sync } = require('fast-glob')
import chalk from 'chalk'
import execa from 'execa'
import glob from 'fast-glob'

const { DependencyGraph, fileVisitor, visitorPlugins } = require('./project-graph')
import { DependencyGraph, fileVisitor, visitorPlugins } from './project-graph/index.mjs'

const getChangedFiles = async (compareTarget = 'origin/main') => {
export const TEST_MATCHING_GLOB = /\.test\.m?js$/gm
const { ava } = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf-8'))

export const getChangedFiles = async (compareTarget = 'origin/main') => {
const { stdout } = await execa('git', ['diff', '--name-only', 'HEAD', compareTarget])
// git is using posix paths so adjust them to the operating system by
// using nodes join function
Expand All @@ -23,10 +27,10 @@ const getChangedFiles = async (compareTarget = 'origin/main') => {
* @param {string[]} changedFiles
* @returns {string[]}
*/
const getAffectedFiles = (changedFiles) => {
export const getAffectedFiles = (changedFiles) => {
// glob is using only posix file paths on windows we need the `\`
// by using join the paths are adjusted to the operating system
const testFiles = sync(['tests/integration/**/*.test.js']).map((filePath) => join(filePath))
const testFiles = glob.sync(['tests/integration/**/*.test.js']).map((filePath) => join(filePath))

// in this case all files are affected
if (
Expand All @@ -44,7 +48,7 @@ const getAffectedFiles = (changedFiles) => {
fileVisitor(file, { graph, visitorPlugins })
})

return [...graph.affected(changedFiles, (file) => file.endsWith('.test.js'))]
return [...graph.affected(changedFiles, (file) => file.match(TEST_MATCHING_GLOB) !== null)]
}

/**
Expand All @@ -63,7 +67,7 @@ const main = async (args) => {
console.log('No files where affected by the changeset!')
return
}
console.log(`Running affected Tests: \n${grey([...affectedFiles].join(', '))}`)
console.log(`Running affected Tests: \n${chalk.grey([...affectedFiles].join(', '))}`)
const testRun = execa('c8', ['-r', 'json', 'ava', ...affectedFiles], {
stdio: 'inherit',
preferLocal: true,
Expand Down Expand Up @@ -92,13 +96,11 @@ const main = async (args) => {
// $ npm run test:affected -- HEAD~1
//
// The default is when running without arguments a git diff target off 'origin/master'
if (require.main === module) {
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const args = process.argv.slice(2)
// eslint-disable-next-line promise/prefer-await-to-callbacks,promise/prefer-await-to-then
main(args).catch((error) => {
console.error(error)
process.exit(1)
})
}

module.exports = { getChangedFiles, getAffectedFiles }
@@ -1,8 +1,7 @@
// @ts-check
import { digraph } from 'graphviz'

const graphviz = require('graphviz')

class DependencyGraph {
export class DependencyGraph {
/** @type {Map<string, Set<string>>} */
graph = new Map()

Expand Down Expand Up @@ -66,10 +65,10 @@ class DependencyGraph {
/**
* Visualizes a dependency graph the output is a graphviz graph
* that can be printed to `.to_dot()` or rendered to a png file
* @returns {graphviz.Graph}
* @returns {import('graphviz').Graph}
*/
visualize() {
const graph = graphviz.digraph('G')
const graph = digraph('G')
this.graph.forEach((edges, node) => {
graph.addNode(node)
edges.forEach((edge) => {
Expand All @@ -80,5 +79,3 @@ class DependencyGraph {
return graph
}
}

module.exports = { DependencyGraph }
@@ -1,17 +1,17 @@
// @ts-check
const { existsSync, readFileSync, statSync } = require('fs')
const { dirname, join, parse } = require('path')
import { existsSync, readFileSync, statSync } from 'fs'
import { dirname, join, parse } from 'path'

const ts = require('typescript')
import ts from 'typescript'

const { DependencyGraph } = require('./dependency-graph')
import { DependencyGraph } from './dependency-graph.mjs'

/**
* tries to resolve a relative javascript module based on its specifier
* @param {string} moduleSpecifier
* @returns {(string|null)}
*/
const resolveRelativeModule = (moduleSpecifier) => {
export const resolveRelativeModule = (moduleSpecifier) => {
if (existsSync(moduleSpecifier) && statSync(moduleSpecifier).isFile()) {
return moduleSpecifier
}
Expand All @@ -21,16 +21,17 @@ const resolveRelativeModule = (moduleSpecifier) => {
if (existsSync(`${moduleSpecifier}/index.js`)) {
return `${moduleSpecifier}/index.js`
}

return null
}

/**
* Parses the dependencies out of a file
* @param {string} fileName
* @param {import('./types').VisitorState} state
* @param {import('./types.d').VisitorState} state
* @param {any} parent
*/
const fileVisitor = function (fileName, state, parent) {
export const fileVisitor = function (fileName, state, parent) {
if (!state) {
state = { graph: new DependencyGraph(), visitorPlugins: [] }
}
Expand Down Expand Up @@ -115,5 +116,3 @@ const fileVisitor = function (fileName, state, parent) {
state.graph.addDependency(parent, fileName)
}
}

module.exports = { fileVisitor, resolveRelativeModule }
9 changes: 0 additions & 9 deletions tools/project-graph/index.js

This file was deleted.

3 changes: 3 additions & 0 deletions tools/project-graph/index.mjs
@@ -0,0 +1,3 @@
export { DependencyGraph } from './dependency-graph.mjs'
export { fileVisitor } from './file-visitor.mjs'
export * from './visitor-plugins.mjs'
5 changes: 3 additions & 2 deletions tools/project-graph/types.d.ts
@@ -1,5 +1,6 @@
import type { Node } from 'typescript';
import type { DependencyGraph } from './dependency-graph';

import type { DependencyGraph } from './dependency-graph.mjs';

export type Dependency = {
fileName: string
Expand All @@ -14,4 +15,4 @@ export type visitorPlugin = (node: Node) => string | undefined
export type VisitorState = {
graph: DependencyGraph
visitorPlugins: visitorPlugin[]
}
}
@@ -1,13 +1,14 @@
// @ts-check
const { join } = require('path')
import { join } from 'path'

const ts = require('typescript')
import ts from 'typescript'

import { resolveRelativeModule } from './file-visitor.mjs'

const COMMANDS = 'src/commands'
const { resolveRelativeModule } = require('./file-visitor')

/** @type {import('./types').visitorPlugin[]} */
module.exports = [
export const visitorPlugins = [
(node) => {
// check if `await execa(cliPath, ['build', ...flags], {` is used for the command
if (
Expand Down
60 changes: 0 additions & 60 deletions tools/tests/affected-files.test.js

This file was deleted.

74 changes: 74 additions & 0 deletions tools/tests/affected-files.test.mjs
@@ -0,0 +1,74 @@
import { join } from 'path'

import test from 'ava'
import glob from 'fast-glob'
import mock from 'mock-fs'
import { stub, createSandbox } from 'sinon'

import { simpleMockedFileSystem } from './utils/file-systems.mjs'

/**
* Get a list of affected files for a mocked file system
* @param {string[]} changedFiles The list of changed files
* @param {Record<string, string>} fileSystem The mocked file system
* @returns Returns a list of affected files
*/
const getAffectedFilesFromMock = async (changedFiles, fileSystem = simpleMockedFileSystem) => {
const mockedTestFiles = Object.keys(fileSystem).filter((file) => file.match(/\.test\.m?js$/gm))
const globStub = stub(glob, 'sync').returns(mockedTestFiles)

const { getAffectedFiles } = await import('../affected-test.mjs')
mock(fileSystem)

const affectedFiles = getAffectedFiles(changedFiles)

mock.restore()
globStub.restore()

return { affectedFiles, mockedTestFiles }
}

test.beforeEach((t) => {
t.context.sandbox = createSandbox()
})

test.afterEach((t) => {
t.context.sandbox.restore()
})

test.only('should get all files marked as affected when the package.json is touched', async (t) => {
const consoleStub = t.context.sandbox.stub(console, 'log').callsFake(() => {})
const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock(['package.json'])

t.truthy(consoleStub.firstCall.calledWith('All files are affected based on the changeset'))
t.deepEqual(affectedFiles, mockedTestFiles)
})

test.serial('should get all files marked as affected when the npm-shrinkwrap.json is touched', async (t) => {
const consoleStub = t.context.sandbox.stub(console, 'log').callsFake(() => {})
const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock(['npm-shrinkwrap.json'])

t.truthy(consoleStub.firstCall.calledWith('All files are affected based on the changeset'))
t.deepEqual(affectedFiles, mockedTestFiles)
})

test.serial('should get all files marked as affected when a leaf is touched that both tests depend on', async (t) => {
const consoleStub = stub(console, 'log').callsFake(() => {})
const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock([join('src/d.js')])

t.truthy(consoleStub.notCalled)
t.deepEqual(affectedFiles, mockedTestFiles)
consoleStub.restore()
})

test.serial('should only one test affected if a file for it was called', async (t) => {
const { affectedFiles } = await getAffectedFilesFromMock([join('src/nested/b.js')])

t.deepEqual(affectedFiles, [join('tests/a.test.js')])
})

test.serial('should not have any file affected if a different file like a readme was affected', async (t) => {
const { affectedFiles } = await getAffectedFilesFromMock(['README.md'])

t.is(affectedFiles.length, 0)
})
@@ -1,6 +1,6 @@
const test = require('ava')
import test from 'ava'

const { DependencyGraph } = require('../project-graph')
import { DependencyGraph } from '../project-graph/index.mjs'

/** @type {DependencyGraph} */
let graph
Expand Down

1 comment on commit 0577c15

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

Package size: 363 MB

Please sign in to comment.