Skip to content

Commit

Permalink
quick cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony Heber committed Nov 1, 2023
1 parent 93a5b68 commit 6f63f6c
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 63 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ docs
package.json.bak.

# -- CLEAN ALL
*.tsbuildinfo
.eslintcache
.wireit
node_modules

# --
Expand Down
5 changes: 5 additions & 0 deletions .sfdevrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"test": {
"testsPath": "test/**/*.test.ts"
},
"wireit": {
"test": {
"dependencies": ["test:compile", "test:only", "lint"]
}
}
}
117 changes: 110 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,126 @@
}
},
"scripts": {
"build": "sf-build",
"build": "wireit",
"clean": "sf-clean",
"clean-all": "sf-clean all",
"clean:lib": "shx rm -rf lib && shx rm -rf coverage && shx rm -rf .nyc_output && shx rm -f oclif.manifest.json",
"compile": "sf-compile",
"format": "sf-format",
"lint": "sf-lint",
"compile": "wireit",
"docs": "sf-docs",
"format": "wireit",
"lint": "wireit",
"postinstall": "yarn husky install",
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "yarn lint",
"prepack": "sf-prepack",
"pretest": "sf-compile-test",
"test": "sf-test",
"test": "wireit",
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel",
"test:only": "wireit",
"version": "oclif readme"
},
"publishConfig": {
"access": "public"
},
"wireit": {
"build": {
"dependencies": [
"compile",
"lint"
]
},
"compile": {
"command": "tsc -p . --pretty --incremental",
"files": [
"src/**/*.ts",
"**/tsconfig.json",
"messages/**"
],
"output": [
"lib/**",
"*.tsbuildinfo"
],
"clean": "if-file-deleted"
},
"format": {
"command": "prettier --write \"+(src|test|schemas)/**/*.+(ts|js|json)|command-snapshot.json\"",
"files": [
"src/**/*.ts",
"test/**/*.ts",
"schemas/**/*.json",
"command-snapshot.json",
".prettier*"
],
"output": []
},
"lint": {
"command": "eslint src test --color --cache --cache-location .eslintcache",
"files": [
"src/**/*.ts",
"test/**/*.ts",
"messages/**",
"**/.eslint*",
"**/tsconfig.json"
],
"output": []
},
"test:compile": {
"command": "tsc -p \"./test\" --pretty",
"files": [
"test/**/*.ts",
"**/tsconfig.json"
],
"output": []
},
"test": {
"dependencies": [
"test:compile",
"test:only",
"lint"
]
},
"test:only": {
"command": "nyc mocha \"test/**/*.test.ts\"",
"env": {
"FORCE_COLOR": "2"
},
"files": [
"test/**/*.ts",
"src/**/*.ts",
"**/tsconfig.json",
".mocha*",
"!*.nut.ts",
".nycrc"
],
"output": []
},
"test:command-reference": {
"command": "\"./bin/dev\" commandreference:generate --erroronwarnings",
"files": [
"src/**/*.ts",
"messages/**",
"package.json"
],
"output": [
"tmp/root"
]
},
"test:deprecation-policy": {
"command": "\"./bin/dev\" snapshot:compare",
"files": [
"src/**/*.ts"
],
"output": [],
"dependencies": [
"compile"
]
},
"test:json-schema": {
"command": "\"./bin/dev\" schema:compare",
"files": [
"src/**/*.ts",
"schemas"
],
"output": []
}
},
"author": "Anthony Heber"
}
3 changes: 2 additions & 1 deletion src/commands/warp/apex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default class Apex extends SfCommand<any> {
summary: messages.getMessage('flags.class.summary'),
char: 'c',
multiple: true,
required: true,
}),
'test-class': Flags.string({
summary: messages.getMessage('flags.test-class.summary'),
Expand Down Expand Up @@ -83,7 +84,7 @@ export default class Apex extends SfCommand<any> {
testClassMatchPatterns: flags['test-class-match-pattern'],
classes: flags.class.map((className) => ({
className,
testClasses: flags['test-class'],
testClasses: flags['test-class'] ?? [],
})),
}).executeWarpTests();
}
Expand Down
1 change: 0 additions & 1 deletion src/index.ts

This file was deleted.

83 changes: 47 additions & 36 deletions src/lib/commands/apex.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable complexity */
/* eslint-disable no-console */
import * as fs from 'fs';
import * as path from 'path';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as process from 'node:process';
import { Connection } from '@salesforce/core';
import { getApexParser } from 'web-tree-sitter-sfapex';
Expand Down Expand Up @@ -47,14 +47,13 @@ interface Mutant {
startPosition: number;
testResults;
status: string;
error: string;
deploymentDurationMs: number;
testExecuteDurationMs: number;
error?: string;
deploymentDurationMs?: number;
testExecuteDurationMs?: number;
}

const isVerboseEnough = (val: Verbosity, minimumVerbosity: Verbosity): boolean => {
return VerbosityVal[val] >= VerbosityVal[minimumVerbosity];
};
const isVerboseEnough = (val: Verbosity, minimumVerbosity: Verbosity): boolean =>
VerbosityVal[val] >= VerbosityVal[minimumVerbosity];

export default class ApexWarper {
// takes in configuration
Expand Down Expand Up @@ -87,18 +86,19 @@ export default class ApexWarper {
this.parser = await getApexParser();
for (const classUnderTest of this.config.classes) {
const totalExecutePerfName = getPerfStart();
if (!classUnderTest.testClasses) {
classUnderTest.testClasses = [];
}
// if no tests are specified, try and locate tests inside the org
const promises = [] as Array<ReturnType<typeof this.usePatternsToGuessAtTestClasses>>;
if (!classUnderTest.testClasses || classUnderTest.testClasses.length === 0) {
await this.usePatternsToGuessAtTestClasses(classUnderTest);
promises.push(this.usePatternsToGuessAtTestClasses(classUnderTest));
}
// eslint-disable-next-line no-await-in-loop
await Promise.all(promises);

if (classUnderTest.testClasses.length === 0) {
throw new Error('No test classes identified, unable to continue');
}
// run tests first to ensure they are valid and passing in the current config
// eslint-disable-next-line no-await-in-loop
let testResults = await executeTests(this.conn, classUnderTest.testClasses, this.config.timeoutMs);
if (testResults.MethodsFailed > 0) {
throw new Error('Tests not passing before modifying target, unable to start warp');
Expand All @@ -107,6 +107,7 @@ export default class ApexWarper {
console.log('All tests passing before warping target');
}
const className = classUnderTest.className;
// eslint-disable-next-line no-await-in-loop
const classes = await getApexClasses(this.conn, [classUnderTest.className]);
const parsePerfName = getPerfStart();
for (const c of classes) {
Expand All @@ -127,40 +128,42 @@ export default class ApexWarper {

// TODO: Need to figure out how to block mutants inside of "ignore" sections
const captures = this.getCaptures(tree, query).filter(
(c) => !(this.config.suppressedRuleNames || []).includes(c.name)
(c) => !(this.config.suppressedRuleNames ?? []).includes(c.name),
);

if (this.atLeastVerbosity(Verbosity.minimal)) {
console.log(
`Found ${captures.length} candidates in ${className}, testing with ${classUnderTest.testClasses.join(', ')}`
`Found ${captures.length} candidates in ${className}, testing with ${classUnderTest.testClasses.join(', ')}`,
);
}
let mutantsKilled = 0;
let count = 0;
this.mutants.set(className, []);
const mutantList: Mutant[] = [];
this.mutants.set(className, mutantList);
for (const capture of captures) {
let finalStatus = 'unknown';
let finalStatusMessage: string;
let finalStatusMessage: string | undefined;
const perfName = getPerfStart();

const oldLines: Lines = {};
for (let i = capture.node.startPosition.row; i <= capture.node.endPosition.row; i++) {
oldLines[i] = lines[i];
lines[i] = ''; // blank out the line so it is easier to inject replacements later
}
let deployDuration: number;
let testPerfDuration: number;
let deployDuration: number | undefined;
let testPerfDuration: number | undefined;
try {
const textParts = getMutatedParts(capture, oldLines);
if (this.atLeastVerbosity(Verbosity.details)) {
this.reportMutant(capture, oldLines, textParts);
reportMutant(capture, oldLines, textParts);
}
lines[capture.node.startPosition.row] = textParts.join('');
// push the file to the org
// TODO: capture compile errors/failures and report that status
this.orgClassIsMutated = true;
const writePerfName = getPerfStart();
if (!this.config.analyzeOnly) {
// eslint-disable-next-line no-await-in-loop
await this.writeApexClassesToOrg(classUnderTest.className, lines.join('\n'));
}
deployDuration = getPerfDurationMs(writePerfName);
Expand All @@ -170,6 +173,7 @@ export default class ApexWarper {
// capture the results against that mutant
const testPerfName = getPerfStart();
if (!this.config.analyzeOnly) {
// eslint-disable-next-line no-await-in-loop
testResults = await executeTests(this.conn, classUnderTest.testClasses, this.config.timeoutMs);
}
testPerfDuration = getPerfDurationMs(testPerfName);
Expand Down Expand Up @@ -211,7 +215,7 @@ export default class ApexWarper {
}
finalStatusMessage = errorMessage;
}
this.mutants.get(className).push({
mutantList.push({
type: capture.name,
startLine: capture.node.startPosition.row,
startPosition: capture.node.startPosition.column,
Expand All @@ -237,11 +241,12 @@ export default class ApexWarper {
`\nKilled ${mutantsKilled}/${count} (${(
(mutantsKilled / count) *
100
).toFixed()}%) in ${getPerfDurationHumanReadable(totalExecutePerfName)}`
).toFixed()}%) in ${getPerfDurationHumanReadable(totalExecutePerfName)}`,
);
}
// put the class back the way we found it, what if they break the command??
// probably best to try and capture the break command and fix the org code
// eslint-disable-next-line no-await-in-loop
await this.writeApexClassesToOrg(classUnderTest.className, originalClassText);
this.orgClassIsMutated = false;
}
Expand All @@ -259,17 +264,6 @@ export default class ApexWarper {
return captures;
}

private reportMutant(capture: QueryCapture, oldText: Lines, newLineParts: string[]): void {
// probably a smarter way to do this out there...
const [start, middle, end] = getTextParts(oldText, capture.node);
console.log(
`Start Line ${capture.node.startPosition.row} | ${capture.name}\n`,
`- ${start}\x1b[32m${middle}\x1b[0m${end}`,
'\n',
`+ ${newLineParts[0]}\x1b[31m${newLineParts[1]}\x1b[0m${newLineParts[2]}`
);
}

private subscribeToSignalToMaybeUnwind(className: string, originalClassText: string): void {
// Using a single function to handle multiple signals
// if the class in the org is currently mutated, it must be restored on term
Expand Down Expand Up @@ -306,7 +300,7 @@ export default class ApexWarper {
if (this.unwindingPromise !== undefined) {
await this.unwindingPromise;
}
return writeApexClassesToOrg(this.conn, this.classMapByName[className].Id || '', body, this.config.timeoutMs);
return writeApexClassesToOrg(this.conn, this.classMapByName[className].Id ?? '', body, this.config.timeoutMs);
}

private async usePatternsToGuessAtTestClasses(classUnderTest: {
Expand All @@ -319,10 +313,27 @@ export default class ApexWarper {
}

const testClassResults = await getApexClasses(this.conn, testClassCandidates);
const promises: Array<Promise<void>> = [];
for (const r of testClassResults) {
if (await isTestClass(r.Body)) {
classUnderTest.testClasses.push(r.Name);
}
promises.push(
isTestClass(r.Body).then((res) => {
if (res) {
classUnderTest.testClasses.push(r.Name);
}
}),
);
}
await Promise.all(promises);
}
}

function reportMutant(capture: QueryCapture, oldText: Lines, newLineParts: string[]): void {
// probably a smarter way to do this out there...
const [start, middle, end] = getTextParts(oldText, capture.node);
console.log(
`Start Line ${capture.node.startPosition.row} | ${capture.name}\n`,
`- ${start}\x1b[32m${middle}\x1b[0m${end}`,
'\n',
`+ ${newLineParts[0]}\x1b[31m${newLineParts[1]}\x1b[0m${newLineParts[2]}`,
);
}
2 changes: 1 addition & 1 deletion src/lib/perf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { performance } from 'perf_hooks';
import { performance } from 'node:perf_hooks';

let perfMarkCount = 0;
export function getPerfStart(): string {
Expand Down
Loading

0 comments on commit 6f63f6c

Please sign in to comment.