Skip to content

Commit

Permalink
feat(cli): log status of cached repositories
Browse files Browse the repository at this point in the history
- Log count of cached repositories and cache location in the beginning and end of scan
- Only used in CLI mode
  • Loading branch information
AriPerkkio committed May 15, 2021
1 parent 3f6fd8e commit dc5580c
Show file tree
Hide file tree
Showing 16 changed files with 160 additions and 28 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ module.exports = {
},
},
{
files: ['*.test.ts*'],
files: ['*.test.ts*', '**/__mocks__/*'],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
},
],
Expand Down
2 changes: 1 addition & 1 deletion lib/file-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export {
RESULTS_COMPARE_LOCATION,
RESULTS_COMPARISON_CACHE_LOCATION,
} from './file-constants';
export { removeCachedRepository } from './repository-client';
export { getCacheStatus, removeCachedRepository } from './repository-client';
export { default as ResultsStore } from './results-store';
export { RESULTS_TEMPLATE_CI_BASE } from './result-templates';
33 changes: 33 additions & 0 deletions lib/file-client/repository-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,36 @@ export async function removeCachedRepository(
fs.rmdirSync(repoLocation, { recursive: true });
}
}

/**
* Get status of cache. Includes count of cached repositories and location of the cache.
*/
export function getCacheStatus(): {
countOfRepositories: number;
location: string;
} {
let countOfRepositories = 0;

if (fs.existsSync(CACHE_LOCATION)) {
// Root level directories are users/organizations, e.g. `@testing-library`
const repositoryOwners = fs.readdirSync(CACHE_LOCATION);

repositoryOwners.forEach(repositoryOwner => {
const stats = fs.statSync(`${CACHE_LOCATION}/${repositoryOwner}`);

// Check type just to be sure
if (!stats.isDirectory()) return;

const repositories = fs.readdirSync(
`${CACHE_LOCATION}/${repositoryOwner}`
);

countOfRepositories += repositories.length;
});
}

return {
countOfRepositories,
location: CACHE_LOCATION,
};
}
9 changes: 6 additions & 3 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { renderApplication } from '@ui';
import config, { validateConfig } from '@config';
import engine from '@engine';
import { LintMessage } from '@engine/types';
import { prepareResultsDirectory, writeResults } from '@file-client';
import * as fileClient from '@file-client';
import logger from '@progress-logger';

/**
Expand All @@ -25,8 +25,10 @@ async function main() {
}

await validateConfig(config);
prepareResultsDirectory();
fileClient.prepareResultsDirectory();

renderApplication();
logger.onCacheStatus(fileClient.getCacheStatus());

// Start x amount of task runners parallel until we are out of repositories to scan
await Promise.all(
Expand All @@ -36,6 +38,7 @@ async function main() {
);

logger.onAllRepositoriesScanned();
logger.onCacheStatus(fileClient.getCacheStatus());
}

/**
Expand Down Expand Up @@ -120,7 +123,7 @@ async function scanRepo(repository: string) {
);

try {
await writeResults(results, repository);
await fileClient.writeResults(results, repository);
} catch (e) {
logger.onWriteFailure(repository, e);
}
Expand Down
5 changes: 5 additions & 0 deletions lib/progress-logger/log-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export const CI_STATUS_TEMPLATE = (
[INFO/STATUS] Errors (${errorCount})
${tasks.map(task => CI_TASK_TEMPLATE(task)).join('\n')}\n`;

export const CACHE_STATUS_TEMPLATE = (
repositoriesCount: number,
location: string
): string => `[INFO] Cached repositories (${repositoriesCount}) at ${location}`;

const CI_TASK_TEMPLATE = (task: Task): string =>
`[INFO]${TASK_TEMPLATE(task)}`.replace(CI_TEMPLATE_TASK_REGEXP, '/');

Expand Down
24 changes: 23 additions & 1 deletion lib/progress-logger/progress-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,28 @@ class ProgressLogger {
}
}

/**
* Log status of cache. Includes count of cached repositories and location of cache.
* Only used in CLI
*/
onCacheStatus(status: { countOfRepositories: number; location: string }) {
if (config.CI) return;

// Prevent logging useless "0 cached repositories" cases
if (status.countOfRepositories <= 0) return;

if (['verbose', 'info'].includes(config.logLevel)) {
this.addNewMessage({
content: Templates.CACHE_STATUS_TEMPLATE(
status.countOfRepositories,
status.location
),
level: 'info',
color: 'yellow',
});
}
}

/**
* Log notification about reaching scan time limit and notify listeners
*/
Expand All @@ -448,7 +470,7 @@ class ProgressLogger {
this.addNewMessage({
content: Templates.DEBUG_TEMPLATE(messages.join('\n')),
color: 'yellow',
level: 'verbose',
level: 'error', // Always visible
});
}
}
Expand Down
6 changes: 1 addition & 5 deletions lib/ui/components/MessagesScrollBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,7 @@ const MessagesScrollBox: React.FC = ({ children }) => {
}

return (
<Box
flexDirection='column'
marginTop={scannedRepositories > 0 ? 1 : 0}
height={areaHeight}
>
<Box flexDirection='column' marginTop={1} height={areaHeight}>
{visibleMessages}
</Box>
);
Expand Down
34 changes: 32 additions & 2 deletions test/integration/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ describe('integration', () => {
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target crashed: unable-to-parse-rule-id
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target 5 errors
[DONE] Finished scan of 1 repositories
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
"
`);
Expand All @@ -221,12 +222,12 @@ describe('integration', () => {
});

test('repositories are cached', async () => {
const cleanRun = await runProductionBuild();
const cleanRun = await runProductionBuild({ CI: true });

expect(cleanRun.output.some(row => /CLONING/.test(row))).toBe(true);
expect(fs.existsSync(REPOSITORY_CACHE)).toBe(true);

const cachedRun = await runProductionBuild();
const cachedRun = await runProductionBuild({ CI: true });

expect(cachedRun.output.some(row => /CLONING/.test(row))).toBe(false);
expect(cachedRun.output.some(row => /PULLING/.test(row))).toBe(true);
Expand All @@ -239,6 +240,33 @@ describe('integration', () => {
expect(fs.existsSync(REPOSITORY_CACHE)).toBe(false);
});

test('cache status is rendered on CLI mode', async () => {
const cleanRun = await runProductionBuild({ CI: false });

expect(cleanRun.output.pop()).toMatchInlineSnapshot(`
"Full log:
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target crashed: unable-to-parse-rule-id
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target 5 errors
[DONE] Finished scan of 1 repositories
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
"
`);

const cachedRun = await runProductionBuild({ CI: false });

expect(cachedRun.output.pop()).toMatchInlineSnapshot(`
"Full log:
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target crashed: unable-to-parse-rule-id
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target 5 errors
[DONE] Finished scan of 1 repositories
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
"
`);
});

// TODO: How to mock setTimeout calls when process is not ran by Jest
test.todo('progress is displayed on CLI mode');

Expand Down Expand Up @@ -1023,6 +1051,7 @@ describe('integration', () => {
test('calls rulesUnderTesting filter with ruleId and repository', async () => {
const { output } = await runProductionBuild({
CI: false,
logLevel: 'verbose',
rulesUnderTesting: (ruleId, options) => {
const { parentPort } = require('worker_threads');

Expand Down Expand Up @@ -1063,6 +1092,7 @@ describe('integration', () => {
eol-last - AriPerkkio/eslint-remote-tester-integration-test-target
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target 21 errors
[DONE] Finished scan of 1 repositories
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
"
`);
Expand Down
4 changes: 2 additions & 2 deletions test/integration/jest.setup.integration.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { addFailureLogger, clearResultsCache } from '../utils';
import { addFailureLogger, clearRepositoryCache } from '../utils';

jest.setTimeout(15000);
addFailureLogger();

beforeEach(async () => {
clearResultsCache();
clearRepositoryCache();

// Timeout between tests - otherwise constant `git clone` calls start failing
await new Promise(r => setTimeout(r, 2000));
Expand Down
4 changes: 2 additions & 2 deletions test/smoke/jest.setup.smoke.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { addFailureLogger, clearResultsCache } from '../utils';
import { addFailureLogger, clearRepositoryCache } from '../utils';

jest.setTimeout(300000);
addFailureLogger();

beforeEach(async () => {
clearResultsCache();
clearRepositoryCache();
});
14 changes: 8 additions & 6 deletions test/smoke/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ describe('smoke', () => {
);

expect(output.pop()).toMatchInlineSnapshot(`
"Full log:
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target 56000 errors
[DONE] Finished scan of 1 repositories
[DONE] Result comparison: Added 56000. Removed 56000.
"Full log:
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
[ERROR] AriPerkkio/eslint-remote-tester-integration-test-target 56000 errors
[DONE] Finished scan of 1 repositories
[INFO] Cached repositories (1) at ./.cache-eslint-remote-tester
[DONE] Result comparison: Added 56000. Removed 56000.
"
`);
"
`);
});
});
4 changes: 4 additions & 0 deletions test/unit/__mocks__/@file-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export const writeResults = jest.fn();
export const prepareResultsDirectory = jest.fn();
export const removeCachedRepository = jest.fn();
export const getFilesMock = jest.fn();
export const getCacheStatus = () => ({
countOfRepositories: 0,
location: 'mock-cache',
});

export const ResultsStore = {
getResults: jest.fn().mockReturnValue(['MOCK_RESULT']),
Expand Down
1 change: 1 addition & 0 deletions test/unit/__mocks__/@progress-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class MockLogger {
onReadFailure = jest.fn();
onWriteFailure = jest.fn();
onLintEnd = jest.fn();
onCacheStatus = jest.fn();

on = jest.fn().mockImplementation((event: ListenerType, listener) => {
const eventListeners = this.mockListeners[event];
Expand Down
5 changes: 5 additions & 0 deletions test/unit/file-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
RESULTS_LOCATION,
RESULTS_COMPARE_DIR,
RESULTS_COMPARISON_CACHE_LOCATION,
CACHE_LOCATION,
} from '@file-client';
import {
Result,
Expand Down Expand Up @@ -50,6 +51,10 @@ function createResults(): void {
function createComparisonCache(...results: Result[]): void {
removeComparisonCache();

if (!fs.existsSync(CACHE_LOCATION)) {
fs.mkdirSync(CACHE_LOCATION);
}

fs.writeFileSync(
RESULTS_COMPARISON_CACHE_LOCATION,
JSON.stringify(results),
Expand Down
22 changes: 22 additions & 0 deletions test/unit/repository-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'fs';

import {
cloneRepository,
getCacheStatus,
removeCachedRepository,
} from '@file-client/repository-client';
import SimpleGit from '__mocks__/simple-git';
Expand Down Expand Up @@ -66,4 +67,25 @@ describe('repository-client', () => {
);
});
});

describe('getCacheStatus', () => {
test('returns count of repositories', () => {
fs.mkdirSync(`${EXPECTED_CACHE}/user-1`);
fs.mkdirSync(`${EXPECTED_CACHE}/user-1/repository-1`);
fs.mkdirSync(`${EXPECTED_CACHE}/user-1/repository-2`);
fs.mkdirSync(`${EXPECTED_CACHE}/user-1/repository-3`);
fs.mkdirSync(`${EXPECTED_CACHE}/user-1/repository-4`);

fs.mkdirSync(`${EXPECTED_CACHE}/user-2`);
fs.mkdirSync(`${EXPECTED_CACHE}/user-2/repository-1`);

fs.mkdirSync(`${EXPECTED_CACHE}/user-3`);

expect(getCacheStatus().countOfRepositories).toBe(5);
});

test('returns location of cache', () => {
expect(getCacheStatus().location).toBe(EXPECTED_CACHE);
});
});
});
18 changes: 13 additions & 5 deletions test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,15 @@ export async function runProductionBuild(
encoding: 'utf8',
cols: 999, // Prevent word wrap
rows: 999, // Prevent ink from erasing the screen,
env: { ...process.env, NODE_OPTIONS: '--max_old_space_size=5120' },
env: {
...process.env,

// For smoke test to not crash
NODE_OPTIONS: '--max_old_space_size=5120',

// Force ink to render even when run in CI
CI: 'false',
},
});
const output: string[] = [];
ptyProcess.onData(data => {
Expand Down Expand Up @@ -149,11 +157,11 @@ function parsePtyOutput(output: string[]): string[] {
}

/**
* Remove possible result caches
* Remove possible repository caches
*/
export function clearResultsCache(): void {
if (fs.existsSync(REPOSITORY_CACHE)) {
fs.rmdirSync(REPOSITORY_CACHE, { recursive: true });
export function clearRepositoryCache(): void {
if (fs.existsSync(CACHE_LOCATION)) {
fs.rmdirSync(CACHE_LOCATION, { recursive: true });
}
}

Expand Down

0 comments on commit dc5580c

Please sign in to comment.