patch-package
Version:
When forking just won't work, patch it.
198 lines (175 loc) • 5.36 kB
text/typescript
import { generate } from "randomstring"
import {
NON_EXECUTABLE_FILE_MODE,
EXECUTABLE_FILE_MODE,
} from "../src/patch/parse"
import { assertNever } from "../src/assertNever"
export interface File {
contents: string
mode: number
}
export interface Files {
[filePath: string]: File
}
export interface TestCase {
cleanFiles: Files
modifiedFiles: Files
}
const fileCharSet = `
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789!@£$%^&*()-=_+[]{};'i\\:"|<>?,./\`~§±
0123456789!@£$%^&*()-=_+[]{};'i\\:"|<>?,./\`~§±
\r\r\r\r\t\t\t\t
`
function makeFileContents(): File {
return {
contents:
generate({
length: Math.floor(Math.random() * 1000),
charset: fileCharSet,
}) || "",
mode: Math.random() > 0.5 ? 0o644 : 0o755,
}
}
function makeFileName(ext?: boolean) {
const name = generate({
length: Math.ceil(Math.random() * 10),
charset: "abcdefghijklmnopqrstuvwxyz-_0987654321",
})
return ext ? name + "." + generate(Math.ceil(Math.random() * 4)) : name
}
function makeFilePath() {
const numParts = Math.floor(Math.random() * 3)
const parts = []
for (let i = 0; i < numParts; i++) {
parts.push(makeFileName())
}
parts.push(makeFileName(true))
return parts.join("/")
}
function makeFiles(): Files {
const fileSystem: Files = {}
const numFiles = Math.random() * 3 + 1
for (let i = 0; i < numFiles; i++) {
fileSystem[makeFilePath()] = makeFileContents()
}
return fileSystem
}
type MutationKind =
| "deleteFile"
| "createFile"
| "deleteLine"
| "insertLine"
| "renameFile"
| "changeFileMode"
const mutationKindLikelihoods: Array<[MutationKind, number]> = [
["deleteFile", 1],
["createFile", 1],
["renameFile", 1],
["changeFileMode", 1],
["deleteLine", 10],
["insertLine", 10],
]
const liklihoodSum = mutationKindLikelihoods.reduce((acc, [_, n]) => acc + n, 0)
function getNextMutationKind(): MutationKind {
const n = Math.random() * liklihoodSum
let sum = 0
for (const [kind, likelihood] of mutationKindLikelihoods) {
sum += likelihood
if (n < sum) {
return kind
}
}
return "insertLine"
}
function selectRandomElement<T>(ts: T[]): T {
return ts[Math.floor(Math.random() * ts.length)]
}
function deleteLinesFromFile(file: File): File {
const numLinesToDelete = Math.ceil(Math.random() * 1)
const lines = file.contents.split("\n")
const index = Math.max(Math.random() * (lines.length - numLinesToDelete), 0)
lines.splice(index, numLinesToDelete)
return { ...file, contents: lines.join("\n") }
}
function insertLinesIntoFile(file: File): File {
const lines = file.contents.split("\n")
const index = Math.floor(Math.random() * lines.length)
const length = Math.ceil(Math.random() * 5)
lines.splice(index, 0, generate({ length, charset: fileCharSet }))
return { ...file, contents: lines.join("\n") }
}
function getUniqueFilename(files: Files) {
let filename = makeFileName()
const ks = Object.keys(files)
while (ks.some(k => k.startsWith(filename))) {
filename = makeFileName()
}
return filename
}
function mutateFiles(files: Files): Files {
const mutatedFiles = { ...files }
const numMutations = Math.ceil(Math.random() * 1000)
for (let i = 0; i < numMutations; i++) {
const mutationKind = getNextMutationKind()
switch (mutationKind) {
case "deleteFile": {
if (Object.keys(mutatedFiles).length === 1) {
break
}
// select a file at random and delete it
const pathToDelete = selectRandomElement(Object.keys(mutatedFiles))
delete mutatedFiles[pathToDelete]
break
}
case "createFile": {
mutatedFiles[getUniqueFilename(mutatedFiles)] = makeFileContents()
break
}
case "deleteLine": {
const pathToDeleteFrom = selectRandomElement(Object.keys(mutatedFiles))
mutatedFiles[pathToDeleteFrom] = deleteLinesFromFile(
mutatedFiles[pathToDeleteFrom],
)
break
}
case "insertLine":
const pathToInsertTo = selectRandomElement(Object.keys(mutatedFiles))
mutatedFiles[pathToInsertTo] = insertLinesIntoFile(
mutatedFiles[pathToInsertTo],
)
// select a file at random and insert some text in there
break
case "renameFile":
const pathToRename = selectRandomElement(Object.keys(mutatedFiles))
mutatedFiles[getUniqueFilename(mutatedFiles)] =
mutatedFiles[pathToRename]
delete mutatedFiles[pathToRename]
break
case "changeFileMode":
const pathToChange = selectRandomElement(Object.keys(mutatedFiles))
const { mode, contents } = mutatedFiles[pathToChange]
mutatedFiles[pathToChange] = {
contents,
mode:
mode === NON_EXECUTABLE_FILE_MODE
? EXECUTABLE_FILE_MODE
: NON_EXECUTABLE_FILE_MODE,
}
break
default:
assertNever(mutationKind)
}
}
return { ...mutatedFiles }
}
export function generateTestCase(): TestCase {
const cleanFiles = makeFiles()
return {
cleanFiles,
modifiedFiles: mutateFiles(cleanFiles),
}
}