template / files / internal / core / profile_test.go
profile_test.go
| 1 | package core |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "fmt" |
| 6 | "io/fs" |
| 7 | "os" |
| 8 | "os/exec" |
| 9 | "os/user" |
| 10 | "path/filepath" |
| 11 | "runtime" |
| 12 | "strings" |
| 13 | "testing" |
| 14 | "time" |
| 15 | |
| 16 | "github.com/stretchr/testify/assert" |
| 17 | ) |
| 18 | |
| 19 | func TestValidateProfile(t *testing.T) { |
| 20 | testSuccess := func(profile Profile) { |
| 21 | err := validateProfile(&profile) |
| 22 | if err != nil { |
| 23 | t.Errorf("Expected to validate profile '%v', but failed: %v", profile.Name, err) |
| 24 | } |
| 25 | } |
| 26 | testFailure := func(profile Profile, expectedErrStr string) { |
| 27 | err := validateProfile(&profile) |
| 28 | assert.NotNil(t, err, "Expected to fail to validate profile '%v', but succeeded") |
| 29 | assert.Contains(t, err.Error(), expectedErrStr, "Expected error to contain '%v', but it didn't", expectedErrStr) |
| 30 | } |
| 31 | |
| 32 | testSuccess(Profile{ |
| 33 | Type: FileType, |
| 34 | Name: "daily", |
| 35 | NamePatterns: []ParameterizedText{"{{.Date}}"}, |
| 36 | TargetDirectory: "~/test/dir", |
| 37 | }) |
| 38 | testSuccess(Profile{ |
| 39 | Type: FileType, |
| 40 | Name: "daily", |
| 41 | NamePatterns: []ParameterizedText{"{{.Date}}"}, |
| 42 | FileTemplate: "foo", |
| 43 | TargetDirectory: "~/test/dir", |
| 44 | }) |
| 45 | |
| 46 | testFailure(Profile{ |
| 47 | Name: "", |
| 48 | }, "profile name is required") |
| 49 | testFailure(Profile{ |
| 50 | Name: "list", |
| 51 | }, "forbidden profile name 'list'") |
| 52 | testFailure(Profile{ |
| 53 | Name: "get", |
| 54 | }, "forbidden profile name 'get'") |
| 55 | testFailure(Profile{ |
| 56 | Type: FileType, |
| 57 | Name: "daily", |
| 58 | }, "namePatterns is required") |
| 59 | testFailure(Profile{ |
| 60 | Type: FileType, |
| 61 | Name: "daily", |
| 62 | NamePatterns: []ParameterizedText{"blah"}, |
| 63 | }, "targetDirectory is not specified for profile daily, which has Type=file") |
| 64 | |
| 65 | testFailure(Profile{ |
| 66 | Type: FolderType, |
| 67 | Name: "daily", |
| 68 | NamePatterns: []ParameterizedText{"test"}, |
| 69 | }, "targetDirectory is not specified for profile daily, which has Type=folder") |
| 70 | testFailure(Profile{ |
| 71 | Type: FolderType, |
| 72 | Name: "daily", |
| 73 | NamePatterns: []ParameterizedText{"test"}, |
| 74 | FileTemplate: "bar", |
| 75 | }, "targetDirectory is not specified for profile daily, which has Type=folder") |
| 76 | testFailure(Profile{ |
| 77 | Type: ScriptType, |
| 78 | Name: "daily", |
| 79 | NamePatterns: []ParameterizedText{"test", "test2"}, |
| 80 | }, "multiple name patterns are not supported for profile daily, which has Type=script") |
| 81 | testFailure(Profile{ |
| 82 | Type: FileType, |
| 83 | Name: "daily", |
| 84 | NamePatterns: []ParameterizedText{"test", "test2"}, |
| 85 | }, "targetDirectory is not specified for profile daily, which has Type=file") |
| 86 | testFailure(Profile{ |
| 87 | Type: FileType, |
| 88 | Name: "daily", |
| 89 | NamePatterns: []ParameterizedText{"test", ""}, |
| 90 | }, "name pattern is required for pattern index 1 of profile daily") |
| 91 | |
| 92 | for _, profile := range defaultConfig().Profiles { |
| 93 | testSuccess(profile) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | func TestFindProfileIndex(t *testing.T) { |
| 98 | config := Config{ |
| 99 | Profiles: []Profile{ |
| 100 | { |
| 101 | Name: "daily", |
| 102 | }, |
| 103 | { |
| 104 | IsDefault: true, |
| 105 | Name: "notes", |
| 106 | }, |
| 107 | }, |
| 108 | } |
| 109 | testSuccess := func(profileName string, expectedProfileName string) { |
| 110 | index, err := findProfileIndex(config.Profiles, profileName) |
| 111 | if err != nil { |
| 112 | t.Errorf("Failed to find profile '%v': %v", profileName, err) |
| 113 | } |
| 114 | profile := config.Profiles[index] |
| 115 | if profile.Name != expectedProfileName { |
| 116 | t.Errorf("Asked for profile '%v', expected '%v', got '%v'", profileName, expectedProfileName, profile.Name) |
| 117 | } |
| 118 | } |
| 119 | testFailure := func(profileName string) { |
| 120 | _, err := findProfileIndex(config.Profiles, profileName) |
| 121 | if err == nil { |
| 122 | t.Errorf("Expected to fail to find profile '%v', but succeeded", profileName) |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | testSuccess("daily", "daily") |
| 127 | testSuccess("notes", "notes") |
| 128 | |
| 129 | testFailure("") |
| 130 | testFailure("non-existent") |
| 131 | |
| 132 | config.Profiles[1].IsDefault = false |
| 133 | testFailure("") |
| 134 | testFailure("non-default") |
| 135 | } |
| 136 | |
| 137 | func TestFindDefaultProfile(t *testing.T) { |
| 138 | _, err := findDefaultProfile(defaultConfig().Profiles) |
| 139 | assert.Nil(t, err, "Expected to find default profile, but failed") |
| 140 | |
| 141 | _, err = findDefaultProfile([]Profile{}) |
| 142 | assert.NotNil(t, err, "Expected to fail to find default profile, but succeeded") |
| 143 | } |
| 144 | |
| 145 | func TestCreateEntity(t *testing.T) { |
| 146 | // setup |
| 147 | now := testNow() |
| 148 | cwd := "~/test/dir" |
| 149 | projectName := "project-name" |
| 150 | extras := map[string]string{ |
| 151 | "foo": "bar", |
| 152 | } |
| 153 | patternData := testPrePatternData() |
| 154 | |
| 155 | testFolder := func() { |
| 156 | monkeyPatchCalls := applyTestMonkeyPatch(t, monkeyPatch{ |
| 157 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, |
| 158 | OSChdir: func(dir string) error { return nil }, |
| 159 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, |
| 160 | }) |
| 161 | |
| 162 | profile := Profile{ |
| 163 | Type: FolderType, |
| 164 | Files: []FileInFolder{ |
| 165 | { |
| 166 | Name: "readme.md", |
| 167 | FileTemplate: "blah", |
| 168 | }, |
| 169 | { |
| 170 | Name: "test.txt", |
| 171 | FileTemplate: "foo", |
| 172 | }, |
| 173 | }, |
| 174 | } |
| 175 | |
| 176 | newName := "new-name" |
| 177 | expectedFullPath := filepath.Clean("~/test/dir/" + newName) |
| 178 | fullPath, err := createEntity(now, profile, cwd, newName, projectName, extras, patternData, nil) |
| 179 | assert.Nil(t, err) |
| 180 | assert.Equal(t, expectedFullPath, fullPath) |
| 181 | assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls)) |
| 182 | assert.Equal(t, expectedFullPath, monkeyPatchCalls.OSChdirCalls[0].dir) |
| 183 | assert.Equal(t, 3, len(monkeyPatchCalls.OSMkdirCalls)) |
| 184 | assert.Equal(t, expectedFullPath, monkeyPatchCalls.OSMkdirCalls[0].path) |
| 185 | assert.Equal(t, 2, len(monkeyPatchCalls.OSWriteFileCalls)) |
| 186 | assert.Equal(t, filepath.Clean(expectedFullPath+"/readme.md"), monkeyPatchCalls.OSWriteFileCalls[0].filename) |
| 187 | assert.Equal(t, []byte("blah"), monkeyPatchCalls.OSWriteFileCalls[0].data) |
| 188 | assert.Equal(t, filepath.Clean(expectedFullPath+"/test.txt"), monkeyPatchCalls.OSWriteFileCalls[1].filename) |
| 189 | assert.Equal(t, []byte("foo"), monkeyPatchCalls.OSWriteFileCalls[1].data) |
| 190 | } |
| 191 | |
| 192 | testFile := func() { |
| 193 | monkeyPatchCalls := applyTestMonkeyPatch(t, monkeyPatch{ |
| 194 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, |
| 195 | }) |
| 196 | |
| 197 | profile := Profile{ |
| 198 | Type: FileType, |
| 199 | FileTemplate: "blah", |
| 200 | } |
| 201 | newName := "new-name.md" |
| 202 | expectedFullPath := filepath.Clean("~/test/dir/" + newName) |
| 203 | fullPath, err := createEntity(now, profile, cwd, newName, projectName, extras, patternData, nil) |
| 204 | assert.Nil(t, err) |
| 205 | assert.Equal(t, expectedFullPath, fullPath) |
| 206 | assert.Equal(t, 1, len(monkeyPatchCalls.OSWriteFileCalls)) |
| 207 | assert.Equal(t, expectedFullPath, monkeyPatchCalls.OSWriteFileCalls[0].filename) |
| 208 | assert.Equal(t, []byte("blah"), monkeyPatchCalls.OSWriteFileCalls[0].data) |
| 209 | } |
| 210 | |
| 211 | testScript := func() { |
| 212 | profile := Profile{ |
| 213 | Type: ScriptType, |
| 214 | PostScripts: []ParameterizedText{ |
| 215 | "{{.ProjectName}}-{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.Extras.key1}}", |
| 216 | }, |
| 217 | } |
| 218 | newName := "new-name.md" |
| 219 | expectedFullPath := filepath.Clean("~/test/dir/" + newName) |
| 220 | fullPath, err := createEntity(now, profile, cwd, newName, projectName, extras, patternData, nil) |
| 221 | assert.Nil(t, err) |
| 222 | assert.Equal(t, expectedFullPath, fullPath) |
| 223 | } |
| 224 | |
| 225 | testFolder() |
| 226 | |
| 227 | testFile() |
| 228 | |
| 229 | testScript() |
| 230 | |
| 231 | // OSMkdir failure for folder type |
| 232 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 233 | OSMkdir: func(path string, perm os.FileMode) error { return fmt.Errorf("mkdir failed") }, |
| 234 | }) |
| 235 | _, err := createEntity(now, Profile{Type: FolderType}, cwd, "new-name", projectName, extras, patternData, nil) |
| 236 | assert.NotNil(t, err) |
| 237 | assert.Contains(t, err.Error(), "error creating directory") |
| 238 | |
| 239 | // OSChdir failure after successful OSMkdir |
| 240 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 241 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, |
| 242 | OSChdir: func(dir string) error { return fmt.Errorf("chdir failed") }, |
| 243 | }) |
| 244 | _, err = createEntity(now, Profile{Type: FolderType}, cwd, "new-name", projectName, extras, patternData, nil) |
| 245 | assert.NotNil(t, err) |
| 246 | assert.Contains(t, err.Error(), "unable to change directory") |
| 247 | |
| 248 | // createFile failure inside folder (FolderType) |
| 249 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 250 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, |
| 251 | OSChdir: func(dir string) error { return nil }, |
| 252 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return fmt.Errorf("write failed") }, |
| 253 | }) |
| 254 | _, err = createEntity(now, Profile{ |
| 255 | Type: FolderType, |
| 256 | Files: []FileInFolder{{Name: "f.md", FileTemplate: "tmpl"}}, |
| 257 | }, cwd, "new-name", projectName, extras, patternData, nil) |
| 258 | assert.NotNil(t, err) |
| 259 | assert.Contains(t, err.Error(), "error creating file") |
| 260 | |
| 261 | // invalid file name pattern inside folder triggers createNewNameForPatterns failure |
| 262 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 263 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, |
| 264 | OSChdir: func(dir string) error { return nil }, |
| 265 | }) |
| 266 | _, err = createEntity(now, Profile{ |
| 267 | Type: FolderType, |
| 268 | Files: []FileInFolder{{Name: ParameterizedText("{{.Bad"), FileTemplate: "tmpl"}}, |
| 269 | }, cwd, "new-name", projectName, extras, patternData, nil) |
| 270 | assert.NotNil(t, err) |
| 271 | assert.Contains(t, err.Error(), "error creating file name for file index") |
| 272 | |
| 273 | // subdir OSMkdir failure inside folder |
| 274 | mkdirCount := 0 |
| 275 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 276 | OSMkdir: func(path string, perm os.FileMode) error { |
| 277 | mkdirCount++ |
| 278 | if mkdirCount > 1 { |
| 279 | return fmt.Errorf("subdir mkdir failed") |
| 280 | } |
| 281 | return nil |
| 282 | }, |
| 283 | OSChdir: func(dir string) error { return nil }, |
| 284 | }) |
| 285 | _, err = createEntity(now, Profile{ |
| 286 | Type: FolderType, |
| 287 | Files: []FileInFolder{{Name: "f.md", FileTemplate: "tmpl"}}, |
| 288 | }, cwd, "new-name", projectName, extras, patternData, nil) |
| 289 | assert.NotNil(t, err) |
| 290 | assert.Contains(t, err.Error(), "error creating sub directory") |
| 291 | |
| 292 | // FileType createFile failure |
| 293 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 294 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return fmt.Errorf("write failed") }, |
| 295 | }) |
| 296 | _, err = createEntity(now, Profile{Type: FileType, FileTemplate: "tmpl"}, cwd, "new-name.md", projectName, extras, patternData, nil) |
| 297 | assert.NotNil(t, err) |
| 298 | assert.Contains(t, err.Error(), "error writing file") |
| 299 | } |
| 300 | |
| 301 | type testFile struct { |
| 302 | name string |
| 303 | isDir bool |
| 304 | } |
| 305 | |
| 306 | func (f testFile) Name() string { return f.name } |
| 307 | func (f testFile) IsDir() bool { return f.isDir } |
| 308 | func (f testFile) Type() fs.FileMode { return 0 } |
| 309 | func (f testFile) Info() (fs.FileInfo, error) { return nil, nil } |
| 310 | func (f testFile) ModTime() time.Time { return time.Time{} } |
| 311 | func (f testFile) Mode() fs.FileMode { return 0 } |
| 312 | func (f testFile) Size() int64 { return 0 } |
| 313 | func (f testFile) Sys() any { return nil } |
| 314 | |
| 315 | var _ os.DirEntry = testFile{} |
| 316 | var _ os.FileInfo = testFile{} |
| 317 | |
| 318 | func createDirEntry(name string, isDir bool) testFile { |
| 319 | entry := testFile{name: name, isDir: isDir} |
| 320 | return entry |
| 321 | } |
| 322 | |
| 323 | func TestProcessProfile(t *testing.T) { |
| 324 | cwd := filepath.Clean("~/test/dir") |
| 325 | var monkeyPatchCalls *monkeyPatchCalls |
| 326 | configureMonkeyPatching := func() { |
| 327 | monkeyPatchCalls = applyTestMonkeyPatch(t, monkeyPatch{ |
| 328 | ExecCommand: func(command string, args ...string) *exec.Cmd { |
| 329 | shell, shellArgs := shellCommandAndArgs("echo") |
| 330 | return exec.Command(shell, shellArgs...) |
| 331 | }, |
| 332 | OSCreate: func(name string) (file *os.File, err error) { |
| 333 | dir := os.TempDir() |
| 334 | return os.CreateTemp(dir, "test") |
| 335 | }, |
| 336 | OSChdir: func(dir string) error { return nil }, |
| 337 | OSGetwd: func() (dir string, err error) { return cwd, nil }, |
| 338 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { |
| 339 | return []os.DirEntry{ |
| 340 | createDirEntry("dir1", true), |
| 341 | createDirEntry("dir2", true), |
| 342 | createDirEntry("file1", false), |
| 343 | createDirEntry("file2", false), |
| 344 | }, nil |
| 345 | }, |
| 346 | OSStat: func(name string) (os.FileInfo, error) { |
| 347 | return createDirEntry("dir1", true), nil |
| 348 | }, |
| 349 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { |
| 350 | return nil |
| 351 | }, |
| 352 | }) |
| 353 | } |
| 354 | |
| 355 | projectName := "project-name" |
| 356 | extras := map[string]string{ |
| 357 | "foo": "bar", |
| 358 | } |
| 359 | |
| 360 | usr, err := user.Current() |
| 361 | assert.Nil(t, err) |
| 362 | assert.NotNil(t, usr) |
| 363 | |
| 364 | testSuccess := func(profile Profile) { |
| 365 | configureMonkeyPatching() |
| 366 | errors := processProfile(profile, projectName, extras, *usr, nil) |
| 367 | assert.Equal(t, 0, len(errors)) |
| 368 | for _, error := range errors { |
| 369 | assert.Nil(t, error) |
| 370 | } |
| 371 | } |
| 372 | testFailure := func(profile Profile, errorCount int, errorStr string) { |
| 373 | configureMonkeyPatching() |
| 374 | errors := processProfile(profile, projectName, extras, *usr, nil) |
| 375 | assert.Equal(t, errorCount, len(errors)) |
| 376 | allErrorsText := "" |
| 377 | for _, err := range errors { |
| 378 | allErrorsText += err.Error() |
| 379 | } |
| 380 | assert.Contains(t, allErrorsText, errorStr) |
| 381 | } |
| 382 | testSingleFailure := func(profile Profile, errorStr string) { |
| 383 | testFailure(profile, 1, errorStr) |
| 384 | } |
| 385 | |
| 386 | // this profile will be built up from failing to eventually passing |
| 387 | currentOS := getCurrentOs() |
| 388 | profile := Profile{ |
| 389 | IsDisabled: true, |
| 390 | } |
| 391 | testSingleFailure(profile, "Profile is disabled, doing nothing") |
| 392 | profile.IsDisabled = false |
| 393 | profile.OS = getOtherOs(currentOS) |
| 394 | testSingleFailure(profile, "this profile is not supported on this OS, doing nothing.") |
| 395 | profile.OS = currentOS |
| 396 | // testSingleFailure(profile, "unable to create post scripts: no post scripts specified") |
| 397 | |
| 398 | profile.NamePatterns = []ParameterizedText{} |
| 399 | profile.Type = FileType |
| 400 | testSingleFailure(profile, "couldn't find a good new name") |
| 401 | |
| 402 | profile.NamePatterns = []ParameterizedText{"file1"} |
| 403 | profile.RunPostscriptOnFailure = true |
| 404 | testSingleFailure(profile, "couldn't find a good new name") |
| 405 | |
| 406 | profile.NamePatterns = []ParameterizedText{"file3"} |
| 407 | profile.PostScripts = []ParameterizedText{"echo {{.ProjectName}}-{{.Year}}"} |
| 408 | profile.TargetDirectory = "./test/dir" |
| 409 | testSuccess(profile) |
| 410 | assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls), "calls to ExecCommand") |
| 411 | assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls), "calls to OSChdir") |
| 412 | assert.Equal(t, 1, len(monkeyPatchCalls.OSGetwdCalls), "calls to OSGetwd") |
| 413 | assert.Equal(t, 1, len(monkeyPatchCalls.OSReadDirCalls), "calls to OSReadDir") |
| 414 | assert.Equal(t, 1, len(monkeyPatchCalls.OSWriteFileCalls), "calls to OSWriteFile") |
| 415 | |
| 416 | profile.PostScripts = []ParameterizedText{"echo ''"} |
| 417 | testSuccess(profile) |
| 418 | assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls), "calls to ExecCommand") |
| 419 | assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls), "calls to OSChdir") |
| 420 | assert.Equal(t, 1, len(monkeyPatchCalls.OSGetwdCalls), "calls to OSGetwd") |
| 421 | assert.Equal(t, 1, len(monkeyPatchCalls.OSReadDirCalls), "calls to OSReadDir") |
| 422 | assert.Equal(t, 1, len(monkeyPatchCalls.OSWriteFileCalls), "calls to OSWriteFile") |
| 423 | |
| 424 | // script-type profile runs a post-script and creates no files |
| 425 | scriptProfile := Profile{ |
| 426 | Type: ScriptType, |
| 427 | Name: "my-script", |
| 428 | PostScripts: []ParameterizedText{"echo hello"}, |
| 429 | } |
| 430 | testSuccess(scriptProfile) |
| 431 | assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls), "script profile should run one command") |
| 432 | assert.Equal(t, 0, len(monkeyPatchCalls.OSWriteFileCalls), "script profile should not write files") |
| 433 | |
| 434 | // pipe input with a folder-type profile is an error |
| 435 | folderProfile := Profile{ |
| 436 | Type: FolderType, |
| 437 | Name: "folder", |
| 438 | NamePatterns: []ParameterizedText{"newfolder"}, |
| 439 | TargetDirectory: "./test/dir", |
| 440 | } |
| 441 | scanner := bufio.NewScanner(strings.NewReader("some input")) |
| 442 | configureMonkeyPatching() |
| 443 | errs := processProfile(folderProfile, projectName, extras, *usr, scanner) |
| 444 | assert.Equal(t, 1, len(errs)) |
| 445 | assert.Contains(t, errs[0].Error(), "cannot use pipe input with non-file type profile") |
| 446 | |
| 447 | // OSGetwd failure |
| 448 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 449 | OSGetwd: func() (string, error) { return "", fmt.Errorf("getwd failed") }, |
| 450 | }) |
| 451 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}}, projectName, extras, *usr, nil) |
| 452 | assert.Equal(t, 1, len(errs)) |
| 453 | assert.Contains(t, errs[0].Error(), "unable to get current working directory") |
| 454 | |
| 455 | // OSReadDir failure |
| 456 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 457 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 458 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return nil, fmt.Errorf("readdir failed") }, |
| 459 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, |
| 460 | OSChdir: func(dir string) error { return nil }, |
| 461 | }) |
| 462 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}, TargetDirectory: "./test/dir"}, projectName, extras, *usr, nil) |
| 463 | assert.Equal(t, 1, len(errs)) |
| 464 | assert.Contains(t, errs[0].Error(), "readdir failed") |
| 465 | |
| 466 | // OSStat returns ErrNotExist → OSMkdir fails |
| 467 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 468 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 469 | OSStat: func(name string) (os.FileInfo, error) { return nil, os.ErrNotExist }, |
| 470 | OSMkdir: func(path string, perm os.FileMode) error { return fmt.Errorf("mkdir target failed") }, |
| 471 | }) |
| 472 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}, TargetDirectory: "./test/dir"}, projectName, extras, *usr, nil) |
| 473 | assert.Equal(t, 1, len(errs)) |
| 474 | assert.Contains(t, errs[0].Error(), "unable to create target directory") |
| 475 | |
| 476 | // OSChdir fails after mkdir succeeds |
| 477 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 478 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 479 | OSStat: func(name string) (os.FileInfo, error) { return nil, os.ErrNotExist }, |
| 480 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, |
| 481 | OSChdir: func(dir string) error { return fmt.Errorf("chdir target failed") }, |
| 482 | }) |
| 483 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}, TargetDirectory: "./test/dir"}, projectName, extras, *usr, nil) |
| 484 | assert.Equal(t, 1, len(errs)) |
| 485 | assert.Contains(t, errs[0].Error(), "unable to change directory (1)") |
| 486 | |
| 487 | // RunPostscriptOnFailure=true, no good name, still runs post script |
| 488 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 489 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 490 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, |
| 491 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { |
| 492 | return []os.DirEntry{createDirEntry("file1", false)}, nil |
| 493 | }, |
| 494 | ExecCommand: func(command string, args ...string) *exec.Cmd { |
| 495 | shell, shellArgs := shellCommandAndArgs("echo") |
| 496 | return exec.Command(shell, shellArgs...) |
| 497 | }, |
| 498 | }) |
| 499 | errs = processProfile(Profile{ |
| 500 | Type: FileType, |
| 501 | NamePatterns: []ParameterizedText{"file1"}, |
| 502 | RunPostscriptOnFailure: true, |
| 503 | PostScripts: []ParameterizedText{"echo hi"}, |
| 504 | }, projectName, extras, *usr, nil) |
| 505 | assert.Equal(t, 0, len(errs)) |
| 506 | |
| 507 | // createEntity error without swallow → "unable to create entity" |
| 508 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 509 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 510 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, |
| 511 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, |
| 512 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { |
| 513 | return fmt.Errorf("write failed") |
| 514 | }, |
| 515 | }) |
| 516 | errs = processProfile(Profile{ |
| 517 | Type: FileType, |
| 518 | NamePatterns: []ParameterizedText{"newfile"}, |
| 519 | }, projectName, extras, *usr, nil) |
| 520 | assert.Equal(t, 1, len(errs)) |
| 521 | assert.Contains(t, errs[0].Error(), "unable to create entity") |
| 522 | |
| 523 | // createEntity error with swallow → continues to post scripts |
| 524 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 525 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 526 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, |
| 527 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, |
| 528 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { |
| 529 | return fmt.Errorf("write failed") |
| 530 | }, |
| 531 | ExecCommand: func(command string, args ...string) *exec.Cmd { |
| 532 | shell, shellArgs := shellCommandAndArgs("echo") |
| 533 | return exec.Command(shell, shellArgs...) |
| 534 | }, |
| 535 | }) |
| 536 | errs = processProfile(Profile{ |
| 537 | Type: FileType, |
| 538 | NamePatterns: []ParameterizedText{"newfile"}, |
| 539 | RunPostscriptOnFailure: true, |
| 540 | PostScripts: []ParameterizedText{"echo hi"}, |
| 541 | }, projectName, extras, *usr, nil) |
| 542 | assert.Equal(t, 0, len(errs)) |
| 543 | |
| 544 | // invalid post script template → createPostScriptArrays failure |
| 545 | configureMonkeyPatching() |
| 546 | errs = processProfile(Profile{ |
| 547 | Type: FileType, |
| 548 | NamePatterns: []ParameterizedText{"file3"}, |
| 549 | TargetDirectory: "./test/dir", |
| 550 | PostScripts: []ParameterizedText{"{{.Bad"}, |
| 551 | }, projectName, extras, *usr, nil) |
| 552 | assert.Equal(t, 1, len(errs)) |
| 553 | assert.Contains(t, errs[0].Error(), "unable to create post scripts") |
| 554 | |
| 555 | // post script with no output → "Post script had no output" |
| 556 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 557 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 558 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, |
| 559 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, |
| 560 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, |
| 561 | ExecCommand: func(command string, args ...string) *exec.Cmd { |
| 562 | return exec.Command("sh", "-c", "true") // exits 0, no stdout output |
| 563 | }, |
| 564 | }) |
| 565 | errs = processProfile(Profile{ |
| 566 | Type: FileType, |
| 567 | NamePatterns: []ParameterizedText{"newfile"}, |
| 568 | PostScripts: []ParameterizedText{"true"}, |
| 569 | }, projectName, extras, *usr, nil) |
| 570 | assert.Equal(t, 0, len(errs)) |
| 571 | |
| 572 | // post script that exits non-zero → logs error but does not fail |
| 573 | applyTestMonkeyPatch(t, monkeyPatch{ |
| 574 | OSGetwd: func() (string, error) { return cwd, nil }, |
| 575 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, |
| 576 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, |
| 577 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, |
| 578 | ExecCommand: func(command string, args ...string) *exec.Cmd { |
| 579 | return exec.Command("sh", "-c", "false") // exits 1 |
| 580 | }, |
| 581 | }) |
| 582 | errs = processProfile(Profile{ |
| 583 | Type: FileType, |
| 584 | NamePatterns: []ParameterizedText{"newfile"}, |
| 585 | PostScripts: []ParameterizedText{"false"}, |
| 586 | }, projectName, extras, *usr, nil) |
| 587 | assert.Equal(t, 0, len(errs)) |
| 588 | } |
| 589 | |
| 590 | func TestCreateNewNameForPatternsGetVersionError(t *testing.T) { |
| 591 | now := testNow() |
| 592 | patternData := testPrePatternData() |
| 593 | // getVersion used in a non-last pattern is an error |
| 594 | _, err := createNewNameForPatterns(now, patternData, "proj", |
| 595 | []ParameterizedText{"{{getVersion}}-name", "fallback"}, |
| 596 | nil, |
| 597 | func(newName string) bool { return false }, // always reject |
| 598 | ) |
| 599 | assert.NotNil(t, err) |
| 600 | assert.Contains(t, err.Error(), "cannot use function getVersion in a pattern that is not the last pattern") |
| 601 | } |
| 602 | |
| 603 | func getCurrentOs() OSType { |
| 604 | os := runtime.GOOS |
| 605 | if os == "windows" { |
| 606 | return "Windows" |
| 607 | } else { |
| 608 | return "Mac" |
| 609 | } |
| 610 | } |
| 611 | func getOtherOs(currentOs OSType) OSType { |
| 612 | if currentOs == "Windows" { |
| 613 | return "Mac" |
| 614 | } |
| 615 | return "Windows" |
| 616 | } |