Skip to content

Commit 828d166

Browse files
committed
don't read files before they exist 🤦‍♂️
1 parent 293ab6a commit 828d166

File tree

5 files changed

+79
-99
lines changed

5 files changed

+79
-99
lines changed

property-based-tests/executeTestCase.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import * as tmp from "tmp"
22
import * as path from "path"
33

44
import { spawnSafeSync } from "../src/spawnSafe"
5-
import { patch } from "../src/patch"
65
import { executeEffects } from "../src/patch/apply"
76
import { parsePatch } from "../src/patch/parse"
7+
import { reversePatch } from "../src/patch/reverse"
88

99
import { TestCase, Files } from "./testCases"
1010
import { appendFileSync, existsSync, writeFileSync } from "fs"
@@ -124,17 +124,17 @@ export function executeTestCase(testCase: TestCase) {
124124
it("works forwards", () => {
125125
fs.setWorkingFiles({ ...testCase.cleanFiles })
126126
reportingFailures(() => {
127-
const effects = patch(patchFileContents)
128-
executeEffects(effects)
127+
const effects = parsePatch(patchFileContents)
128+
executeEffects(effects, { dryRun: false })
129129
expect(fs.getWorkingFiles()).toEqual(testCase.modifiedFiles)
130130
})
131131
})
132132

133133
it("works backwards", () => {
134134
fs.setWorkingFiles({ ...testCase.modifiedFiles })
135135
reportingFailures(() => {
136-
const result = patch(patchFileContents, { reverse: true })
137-
executeEffects(result)
136+
const effects = reversePatch(parsePatch(patchFileContents))
137+
executeEffects(effects, { dryRun: false })
138138
expect(fs.getWorkingFiles()).toEqual(testCase.cleanFiles)
139139
})
140140
})

property-based-tests/testCases.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,17 @@ function makeFiles(): Files {
6363
return fileSystem
6464
}
6565

66-
type MutationKind = "deleteFile" | "createFile" | "deleteLine" | "insertLine"
66+
type MutationKind =
67+
| "deleteFile"
68+
| "createFile"
69+
| "deleteLine"
70+
| "insertLine"
71+
| "renameFile"
6772

6873
const mutationKindLikelihoods: Array<[MutationKind, number]> = [
6974
["deleteFile", 1],
7075
["createFile", 1],
76+
["renameFile", 1],
7177
["deleteLine", 10],
7278
["insertLine", 10],
7379
]
@@ -105,6 +111,15 @@ function insertLinesIntoFile(file: File): File {
105111
return { ...file, contents: lines.join("\n") }
106112
}
107113

114+
function getUniqueFilename(files: Files) {
115+
let filename = makeFileName()
116+
const ks = Object.keys(files)
117+
while (ks.some(k => k.startsWith(filename))) {
118+
filename = makeFileName()
119+
}
120+
return filename
121+
}
122+
108123
function mutateFiles(files: Files): Files {
109124
const mutatedFiles = { ...files }
110125

@@ -122,12 +137,7 @@ function mutateFiles(files: Files): Files {
122137
break
123138
}
124139
case "createFile": {
125-
// TODO make sure there isn't a dir with that filename already
126-
let filename = makeFileName()
127-
while (Object.keys(mutatedFiles).some(k => k.startsWith(filename))) {
128-
filename = makeFileName()
129-
}
130-
mutatedFiles[filename] = makeFileContents()
140+
mutatedFiles[getUniqueFilename(mutatedFiles)] = makeFileContents()
131141
break
132142
}
133143
case "deleteLine": {
@@ -144,10 +154,16 @@ function mutateFiles(files: Files): Files {
144154
)
145155
// select a file at random and insert some text in there
146156
break
157+
case "renameFile":
158+
const pathToRename = selectRandomElement(Object.keys(mutatedFiles))
159+
mutatedFiles[getUniqueFilename(mutatedFiles)] =
160+
mutatedFiles[pathToRename]
161+
delete mutatedFiles[pathToRename]
162+
break
147163
}
148164
}
149165

150-
return mutatedFiles
166+
return { ...mutatedFiles }
151167
}
152168

153169
export function generateTestCase(): TestCase {

src/applyPatches.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { bold, cyan, green, red, yellow } from "chalk"
22
import { getPatchFiles } from "./patchFs"
3-
import { patch } from "./patch"
43
import { executeEffects } from "./patch/apply"
54
import { existsSync, readFileSync } from "fs-extra"
65
import { join, resolve } from "./path"
76
import { posix } from "path"
87
import { getPackageDetailsFromPatchFilename } from "./PackageDetails"
8+
import { parsePatch } from "./patch/parse"
9+
import { reversePatch } from "./patch/reverse"
910

1011
function findPatchFiles(patchesDirectory: string): string[] {
1112
if (!existsSync(patchesDirectory)) {
@@ -114,14 +115,12 @@ export const applyPatch = (
114115
reverse: boolean,
115116
): boolean => {
116117
const patchFileContents = readFileSync(patchFilePath).toString()
118+
const patch = parsePatch(patchFileContents)
117119
try {
118-
const result = patch(patchFileContents, {
119-
reverse,
120-
})
121-
executeEffects(result)
120+
executeEffects(reverse ? reversePatch(patch) : patch, { dryRun: false })
122121
} catch (e) {
123122
try {
124-
patch(patchFileContents, { reverse: !reverse })
123+
executeEffects(reverse ? patch : reversePatch(patch), { dryRun: true })
125124
} catch (e) {
126125
return false
127126
}

src/patch/apply.ts

Lines changed: 45 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,53 @@ import fs from "fs-extra"
22
import { dirname } from "path"
33
import { ParsedPatchFile, FilePatch } from "./parse"
44

5-
export type Effect =
6-
| {
7-
type: "file deletion"
8-
path: string
9-
lines: string[]
10-
mode: number
11-
noNewlineAtEndOfFile: boolean
12-
}
13-
| {
14-
type: "rename"
15-
fromPath: string
16-
toPath: string
17-
}
18-
| {
19-
type: "file creation"
20-
path: string
21-
lines: string[]
22-
mode: number
23-
noNewlineAtEndOfFile: boolean
24-
}
25-
| {
26-
type: "replace"
27-
path: string
28-
lines: string[]
29-
}
30-
31-
export const executeEffects = (effects: Effect[]) => {
5+
export const executeEffects = (
6+
effects: ParsedPatchFile,
7+
{ dryRun }: { dryRun: boolean },
8+
) => {
329
effects.forEach(eff => {
3310
switch (eff.type) {
3411
case "file deletion":
35-
fs.unlinkSync(eff.path)
12+
if (dryRun) {
13+
if (!fs.existsSync(eff.path)) {
14+
throw new Error(
15+
"Trying to delete file that doesn't exist: " + eff.path,
16+
)
17+
}
18+
} else {
19+
fs.unlinkSync(eff.path)
20+
}
3621
break
3722
case "rename":
38-
fs.moveSync(eff.fromPath, eff.toPath)
23+
if (dryRun) {
24+
// TODO: see what patch files look like if moving to exising path
25+
if (!fs.existsSync(eff.fromPath)) {
26+
throw new Error(
27+
"Trying to move file that doesn't exist: " + eff.fromPath,
28+
)
29+
}
30+
} else {
31+
fs.moveSync(eff.fromPath, eff.toPath)
32+
}
3933
break
4034
case "file creation":
41-
fs.ensureDirSync(dirname(eff.path))
42-
fs.writeFileSync(
43-
eff.path,
44-
eff.lines.join("\n") + (eff.noNewlineAtEndOfFile ? "" : "\n"),
45-
{ mode: eff.mode },
46-
)
35+
if (dryRun) {
36+
if (fs.existsSync(eff.path)) {
37+
throw new Error(
38+
"Trying to create file that already exists: " + eff.path,
39+
)
40+
}
41+
} else {
42+
fs.ensureDirSync(dirname(eff.path))
43+
fs.writeFileSync(
44+
eff.path,
45+
eff.lines.join("\n") + (eff.noNewlineAtEndOfFile ? "" : "\n"),
46+
{ mode: eff.mode },
47+
)
48+
}
4749
break
48-
case "replace":
49-
fs.writeFileSync(eff.path, eff.lines.join("\n"))
50+
case "patch":
51+
applyPatch(eff, { dryRun })
5052
break
5153
}
5254
})
@@ -88,7 +90,10 @@ function assertLineEquality(onDisk: string, expected: string) {
8890
*
8991
*/
9092

91-
function applyPatch({ parts, path }: FilePatch): Effect {
93+
function applyPatch(
94+
{ parts, path }: FilePatch,
95+
{ dryRun }: { dryRun: boolean },
96+
): void {
9297
// modifying the file in place
9398
const fileContents = fs.readFileSync(path).toString()
9499

@@ -149,27 +154,7 @@ function applyPatch({ parts, path }: FilePatch): Effect {
149154
}
150155
}
151156

152-
return {
153-
type: "replace",
154-
path,
155-
lines: fileLines,
157+
if (!dryRun) {
158+
fs.writeFileSync(path, fileLines.join("\n"))
156159
}
157160
}
158-
159-
export const applyPatchFile = (patch: ParsedPatchFile): Effect[] => {
160-
const effects: Effect[] = []
161-
162-
for (const part of patch) {
163-
switch (part.type) {
164-
case "file creation":
165-
case "file deletion":
166-
case "rename":
167-
effects.push(part)
168-
break
169-
case "patch":
170-
effects.push(applyPatch(part))
171-
}
172-
}
173-
174-
return effects
175-
}

src/patch/index.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)