Merge pull request #2226 from spotify/rugvip/wintest

Make tests work on Windows and run as part of Windows master build
This commit is contained in:
Patrik Oldsberg
2020-09-07 13:50:18 +02:00
committed by GitHub
14 changed files with 153 additions and 116 deletions
+20 -3
View File
@@ -38,6 +38,23 @@ jobs:
- name: yarn install
run: yarn install --frozen-lockfile
# End of yarn setup
# Tests are broken on Windows, disabled for now
# - name: test
# run: yarn lerna -- run test
- name: lint
run: yarn lerna -- run lint
- name: type checking and declarations
run: yarn tsc --incremental false
- name: verify type dependencies
run: yarn lint:type-deps
- name: test
run: yarn lerna -- run test
- name: Discord notification
if: ${{ failure() }}
uses: Ilshidur/action-discord@0.2.0
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
with:
args: 'Windows master build failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}'
+4 -1
View File
@@ -106,7 +106,10 @@ export function findOwnRootDir(ownDir: string) {
*/
export function findPaths(searchDir: string): Paths {
const ownDir = findOwnDir(searchDir);
const targetDir = fs.realpathSync(process.cwd());
// Drive letter can end up being lowercased here on Windows, bring back to uppercase for consistency
const targetDir = fs
.realpathSync(process.cwd())
.replace(/^[a-z]:/, str => str.toUpperCase());
// Lazy load this as it will throw an error if we're not inside the Backstage repo.
let ownRoot = '';
@@ -59,7 +59,7 @@ describe('createPlugin', () => {
await createTemporaryPluginFolder(tempDir);
await movePlugin(tempDir, pluginDir, id);
await expect(fs.pathExists(pluginDir)).resolves.toBe(true);
expect(pluginDir).toMatch(`/plugins\/${id}`);
expect(pluginDir).toMatch(path.join('', 'plugins', id));
} finally {
await del(tempDir, { force: true });
await del(rootDir, { force: true });
@@ -39,7 +39,7 @@ const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`;
const tempDir = path.join(os.tmpdir(), 'remove-plugin-test');
const removeEmptyLines = (file: string): string =>
file.split('\n').filter(Boolean).join('\n');
file.split(/\r?\n/).filter(Boolean).join('\n');
const createTestPackageFile = async (
testFilePath: string,
@@ -100,7 +100,7 @@ describe('removePlugin', () => {
const packageFileContent = removeEmptyLines(
fse.readFileSync(packageFilePath, 'utf8'),
);
expect(testFileContent === packageFileContent).toBe(true);
expect(testFileContent).toBe(packageFileContent);
} finally {
fse.removeSync(testFilePath);
}
@@ -117,7 +117,7 @@ describe('removePlugin', () => {
const pluginsFileContent = removeEmptyLines(
fse.readFileSync(pluginsFilePaths, 'utf8'),
);
expect(testFileContent === pluginsFileContent).toBe(true);
expect(testFileContent).toBe(pluginsFileContent);
} finally {
fse.removeSync(testFilePath);
}
@@ -138,7 +138,7 @@ describe('removePlugin', () => {
'test@gmail.com',
]);
await removePluginFromCodeOwners(testFilePath, testPluginName);
expect(testFileContent === codeOwnersFileContent).toBeTruthy();
expect(testFileContent).toBe(codeOwnersFileContent);
} finally {
if (fse.existsSync(testFilePath)) fse.removeSync(testFilePath);
}
+3 -1
View File
@@ -37,8 +37,10 @@
},
"devDependencies": {
"@types/jest": "^26.0.7",
"@types/mock-fs": "^4.10.0",
"@types/node": "^12.0.0",
"@types/yup": "^0.28.2"
"@types/yup": "^0.28.2",
"mock-fs": "^4.13.0"
},
"files": [
"dist"
+38 -35
View File
@@ -14,46 +14,48 @@
* limitations under the License.
*/
const pathExists = jest.fn();
jest.mock('fs-extra', () => ({ pathExists }));
import mockFs from 'mock-fs';
import { resolveStaticConfig } from './resolver';
function normalizePaths(paths: string[]) {
return paths.map(p =>
p
.replace(/^[a-z]:/i, '')
.split('\\')
.join('/'),
);
}
describe('resolveStaticConfig', () => {
afterEach(() => {
jest.resetAllMocks();
mockFs.restore();
});
it('should resolve no files for empty roots', async () => {
mockFs({});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: [],
});
expect(resolved).toEqual([]);
expect(pathExists).not.toHaveBeenCalled();
expect(normalizePaths(resolved)).toEqual([]);
});
it('should resolve a single app-config', async () => {
pathExists.mockImplementation(async (path: string) =>
['/repo/app-config.yaml'].includes(path),
);
mockFs({ '/repo/app-config.yaml': '' });
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: ['/repo'],
});
expect(resolved).toEqual(['/repo/app-config.yaml']);
expect(pathExists).toHaveBeenCalledTimes(4);
expect(normalizePaths(resolved)).toEqual(['/repo/app-config.yaml']);
});
it('should resolve a app-configs in different directories', async () => {
pathExists.mockImplementation(async (path: string) =>
['/repo/app-config.yaml', '/repo/packages/a/app-config.yaml'].includes(
path,
),
);
mockFs({
'/repo/app-config.yaml': '',
'/repo/packages/a/app-config.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: [
@@ -64,53 +66,54 @@ describe('resolveStaticConfig', () => {
],
});
expect(resolved).toEqual([
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/packages/a/app-config.yaml',
]);
expect(pathExists).toHaveBeenCalledTimes(16);
});
it('should resolve env and local configs', async () => {
pathExists.mockImplementation(async (path: string) =>
[
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.production.yaml',
'/repo/app-config.production.local.yaml',
'/repo/app-config.development.local.yaml',
'/repo/packages/a/app-config.development.yaml',
'/repo/packages/a/app-config.local.yaml',
].includes(path),
);
mockFs({
'/repo/app-config.yaml': '',
'/repo/app-config.local.yaml': '',
'/repo/app-config.production.yaml': '',
'/repo/app-config.production.local.yaml': '',
'/repo/app-config.development.local.yaml': '',
'/repo/packages/a/app-config.development.yaml': '',
'/repo/packages/a/app-config.local.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: ['/repo', '/repo/packages/a'],
});
expect(resolved).toEqual([
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.development.local.yaml',
'/repo/packages/a/app-config.local.yaml',
'/repo/packages/a/app-config.development.yaml',
]);
expect(pathExists).toHaveBeenCalledTimes(8);
});
it('resolves suffixed configs in the correct order', async () => {
pathExists.mockImplementation(async () => true);
mockFs({
'/repo/app-config.yaml': '',
'/repo/app-config.local.yaml': '',
'/repo/app-config.production.yaml': '',
'/repo/app-config.production.local.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'production',
rootPaths: ['/repo'],
});
expect(resolved).toEqual([
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.production.yaml',
'/repo/app-config.production.local.yaml',
]);
expect(pathExists).toHaveBeenCalledTimes(4);
});
});
+22 -37
View File
@@ -15,45 +15,30 @@
*/
import { loadConfig } from './loader';
jest.mock('fs-extra', () => {
const mockFiles: { [path in string]: string } = {
'/root/app-config.yaml': `
app:
title: Example App
sessionKey:
$secret:
file: secrets/session-key.txt
`,
'/root/app-config.development.yaml': `
app:
sessionKey: development-key
`,
'/root/secrets/session-key.txt': 'abc123',
'/secret-port/app-config.yaml': `
backend:
listen:
port:
$secret:
file: secrets/port.txt
`,
'/secret-port/secrets/port.txt': '12345',
};
return {
async readFile(path: string) {
if (path in mockFiles) {
return mockFiles[path];
}
throw new Error(`File not found, ${path}`);
},
async pathExists(path: string) {
return path in mockFiles;
},
};
});
import mockFs from 'mock-fs';
describe('loadConfig', () => {
beforeAll(() => {
mockFs({
'/root/app-config.yaml': `
app:
title: Example App
sessionKey:
$secret:
file: secrets/session-key.txt
`,
'/root/app-config.development.yaml': `
app:
sessionKey: development-key
`,
'/root/secrets/session-key.txt': 'abc123',
});
});
afterAll(() => {
mockFs.restore();
});
it('loads config without secrets', async () => {
await expect(
loadConfig({
@@ -15,21 +15,21 @@
*/
import { defaultConfigLoader } from './createApp';
import { AppConfig } from '@backstage/config';
(process as any).env = { NODE_ENV: 'test' };
const anyEnv = process.env as any;
describe('defaultConfigLoader', () => {
afterEach(() => {
delete process.env.APP_CONFIG;
delete anyEnv.APP_CONFIG;
});
it('loads static config', async () => {
Object.defineProperty(process.env, 'APP_CONFIG', {
configurable: true,
value: [
{ data: { my: 'config' }, context: 'a' },
{ data: { my: 'override-config' }, context: 'b' },
] as AppConfig[],
});
anyEnv.APP_CONFIG = [
{ data: { my: 'config' }, context: 'a' },
{ data: { my: 'override-config' }, context: 'b' },
];
const configs = await defaultConfigLoader();
expect(configs).toEqual([
{ data: { my: 'config' }, context: 'a' },
@@ -38,13 +38,11 @@ describe('defaultConfigLoader', () => {
});
it('loads runtime config', async () => {
Object.defineProperty(process.env, 'APP_CONFIG', {
configurable: true,
value: [
{ data: { my: 'override-config' }, context: 'a' },
{ data: { my: 'config' }, context: 'b' },
] as AppConfig[],
});
anyEnv.APP_CONFIG = [
{ data: { my: 'override-config' }, context: 'a' },
{ data: { my: 'config' }, context: 'b' },
];
const configs = await (defaultConfigLoader as any)(
'{"my":"runtime-config"}',
);
@@ -62,20 +60,14 @@ describe('defaultConfigLoader', () => {
});
it('fails to load invalid static config', async () => {
Object.defineProperty(process.env, 'APP_CONFIG', {
configurable: true,
value: { my: 'invalid-config' } as any,
});
anyEnv.APP_CONFIG = { my: 'invalid-config' };
await expect(defaultConfigLoader()).rejects.toThrow(
'Static configuration has invalid format',
);
});
it('fails to load bad runtime config', async () => {
Object.defineProperty(process.env, 'APP_CONFIG', {
configurable: true,
value: [{ data: { my: 'config' }, context: 'a' }] as AppConfig[],
});
anyEnv.APP_CONFIG = [{ data: { my: 'config' }, context: 'a' }];
await expect((defaultConfigLoader as any)('}')).rejects.toThrow(
'Failed to load runtime configuration, SyntaxError: Unexpected token } in JSON at position 0',
@@ -47,21 +47,21 @@ describe('createRouter', () => {
const response = await request(app).get('/index.html');
expect(response.status).toBe(200);
expect(response.text).toBe('this is index.html\n');
expect(response.text.trim()).toBe('this is index.html');
});
it('returns other.html', async () => {
const response = await request(app).get('/other.html');
expect(response.status).toBe(200);
expect(response.text).toBe('this is other.html\n');
expect(response.text.trim()).toBe('this is other.html');
});
it('returns index.html if missing', async () => {
const response = await request(app).get('/missing.html');
expect(response.status).toBe(200);
expect(response.text).toBe('this is index.html\n');
expect(response.text.trim()).toBe('this is index.html');
});
});
@@ -83,11 +83,11 @@ describe('createRouter with static fallback handler', () => {
const response1 = await request(app).get('/static/main.txt');
expect(response1.status).toBe(200);
expect(response1.text).toBe('this is main.txt\n');
expect(response1.text.trim()).toBe('this is main.txt');
const response2 = await request(app).get('/static/test.txt');
expect(response2.status).toBe(200);
expect(response2.text).toBe('this is test.txt');
expect(response2.text.trim()).toBe('this is test.txt');
const response3 = await request(app).get('/static/missing.txt');
expect(response3.status).toBe(404);
@@ -96,6 +96,8 @@ describe('GitHubPreparer', () => {
mockEntity.spec.path = './template/test/1/2/3';
const response = await preparer.prepare(mockEntity);
expect(response).toMatch(new RegExp(/\/template\/test\/1\/2\/3$/));
expect(response.split('\\').join('/')).toMatch(
/\/template\/test\/1\/2\/3$/,
);
});
});
@@ -20,6 +20,13 @@ import Docker from 'dockerode';
import { runDockerContainer } from './helpers';
describe('helpers', () => {
if (process.platform === 'win32') {
// eslint-disable-next-line jest/no-focused-tests
it.only('should skip tests on windows', () => {
expect('test').not.toBe('run');
});
}
const mockDocker = new Docker() as jest.Mocked<Docker>;
beforeEach(() => {
@@ -17,6 +17,13 @@ import { DirectoryPreparer } from './dir';
import { getVoidLogger } from '@backstage/backend-common';
import { checkoutGitRepository } from './helpers';
function normalizePath(path: string) {
return path
.replace(/^[a-z]:/i, '')
.split('\\')
.join('/');
}
jest.mock('./helpers', () => ({
...jest.requireActual<{}>('./helpers'),
checkoutGitRepository: jest.fn(() => '/tmp/backstage-repo/org/name/branch/'),
@@ -47,7 +54,7 @@ describe('directory preparer', () => {
'backstage.io/techdocs-ref': 'dir:./our-documentation',
});
expect(await directoryPreparer.prepare(mockEntity)).toEqual(
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
'/directory/our-documentation',
);
});
@@ -61,7 +68,7 @@ describe('directory preparer', () => {
'backstage.io/techdocs-ref': 'dir:/our-documentation/techdocs',
});
expect(await directoryPreparer.prepare(mockEntity)).toEqual(
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
'/our-documentation/techdocs',
);
});
@@ -75,7 +82,7 @@ describe('directory preparer', () => {
'backstage.io/techdocs-ref': 'dir:./docs',
});
expect(await directoryPreparer.prepare(mockEntity)).toEqual(
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
'/tmp/backstage-repo/org/name/branch/docs',
);
expect(checkoutGitRepository).toHaveBeenCalledTimes(1);
@@ -18,6 +18,13 @@ import { getVoidLogger } from '@backstage/backend-common';
import { GithubPreparer } from './github';
import { checkoutGithubRepository } from './helpers';
function normalizePath(path: string) {
return path
.replace(/^[a-z]:/i, '')
.split('\\')
.join('/');
}
jest.mock('./helpers', () => ({
...jest.requireActual<{}>('./helpers'),
checkoutGithubRepository: jest.fn(
@@ -51,7 +58,7 @@ describe('github preparer', () => {
const tempDocsPath = await preparer.prepare(mockEntity);
expect(checkoutGithubRepository).toHaveBeenCalledTimes(1);
expect(tempDocsPath).toEqual(
expect(normalizePath(tempDocsPath)).toEqual(
'/tmp/backstage-repo/org/name/branch/plugins/techdocs-backend/examples/documented-component',
);
});
+12
View File
@@ -4496,6 +4496,13 @@
dependencies:
"@types/node" "*"
"@types/mock-fs@^4.10.0":
version "4.10.0"
resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.10.0.tgz#460061b186993d76856f669d5317cda8a007c24b"
integrity sha512-FQ5alSzmHMmliqcL36JqIA4Yyn9jyJKvRSGV3mvPh108VFatX7naJDzSG4fnFQNZFq9dIx0Dzoe6ddflMB2Xkg==
dependencies:
"@types/node" "*"
"@types/morgan@^1.9.0":
version "1.9.1"
resolved "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.1.tgz#6457872df95647c1dbc6b3741e8146b71ece74bf"
@@ -14793,6 +14800,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdir
dependencies:
minimist "^1.2.5"
mock-fs@^4.13.0:
version "4.13.0"
resolved "https://registry.npmjs.org/mock-fs/-/mock-fs-4.13.0.tgz#31c02263673ec3789f90eb7b6963676aa407a598"
integrity sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA==
modify-values@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"