-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadvanced_scenarios_test.go
More file actions
564 lines (443 loc) · 19.7 KB
/
advanced_scenarios_test.go
File metadata and controls
564 lines (443 loc) · 19.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// TestAdvancedMultiFileScenarios tests scenarios involving multiple files
func TestAdvancedMultiFileScenarios(t *testing.T) {
t.Run("multi-file changes with selective staging", func(t *testing.T) {
scenario := createTestScenario(t, "multi_file")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create multiple files
files := map[string]string{
"main.go": "package main\n\nfunc main() {\n println(\"Hello\")\n}\n",
"utils.go": "package main\n\nfunc helper() string {\n return \"help\"\n}\n",
"config.txt": "debug=true\nport=8080\n",
}
for filename, content := range files {
scenario.commitFile(filename, content, fmt.Sprintf("Add %s", filename))
}
// Modify all files
scenario.modifyFile("main.go", "package main\n\nfunc main() {\n println(\"Hello World\")\n helper()\n}\n")
scenario.modifyFile("utils.go", "package main\n\nfunc helper() string {\n return \"assistance\"\n}\n")
scenario.modifyFile("config.txt", "debug=false\nport=9090\ntimeout=30\n")
// Test staging specific lines in different files
err := stageLines("main.go", []int{4})
if err != nil {
t.Fatal("Failed to stage main.go:", err)
}
err = stageLines("config.txt", []int{2})
if err != nil {
t.Fatal("Failed to stage config.txt:", err)
}
// Verify selective staging across files
mainDiff := scenario.runGitCommand("diff", "--cached", "main.go")
configDiff := scenario.runGitCommand("diff", "--cached", "config.txt")
utilsDiff := scenario.runGitCommand("diff", "--cached", "utils.go")
if !strings.Contains(mainDiff, "Hello World") {
t.Error("main.go change should be staged")
}
if !strings.Contains(configDiff, "port=9090") {
t.Error("config.txt port change should be staged")
}
if utilsDiff != "" {
t.Error("utils.go should not have staged changes")
}
})
}
// TestAdvancedBranchScenarios tests branch and merge scenarios
func TestAdvancedBranchScenarios(t *testing.T) {
t.Run("fixup commit across branches", func(t *testing.T) {
scenario := createTestScenario(t, "branch_fixup")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create initial commit on main
mainCommit := scenario.commitFile(scenario.filename, "main branch content\n", "Main feature")
// Create feature branch
scenario.runGitCommand("checkout", "-b", "feature")
_ = scenario.commitFile(scenario.filename, "main branch content\nfeature addition\n", "Feature addition")
// Switch back to main and make more commits
scenario.runGitCommand("checkout", "main")
scenario.commitFile(scenario.filename, "main branch content\nmain update\n", "Main update")
// Modify file and try to fixup the original main commit
scenario.modifyFile(scenario.filename, "UPDATED main branch content\nmain update\n")
err := stageLines(scenario.filename, []int{1})
if err != nil {
t.Fatal("Failed to stage line:", err)
}
err = createFixupCommit(mainCommit)
if err != nil {
t.Fatal("Failed to create fixup commit:", err)
}
// Verify fixup commit was created
log := scenario.runGitCommand("log", "--oneline", "-1")
if !strings.Contains(log, "fixup!") {
t.Error("Expected fixup commit to be created")
}
})
t.Run("merge conflict scenarios", func(t *testing.T) {
scenario := createTestScenario(t, "merge_conflict")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create base commit
scenario.commitFile(scenario.filename, "line 1\nline 2\nline 3\n", "Base commit")
// Create and commit on branch A
scenario.runGitCommand("checkout", "-b", "branch-a")
scenario.commitFile(scenario.filename, "line 1 - branch A\nline 2\nline 3\n", "Branch A change")
// Switch to branch B and make conflicting change
scenario.runGitCommand("checkout", "main")
scenario.runGitCommand("checkout", "-b", "branch-b")
scenario.commitFile(scenario.filename, "line 1 - branch B\nline 2\nline 3\n", "Branch B change")
// Switch back to main and attempt merge
scenario.runGitCommand("checkout", "main")
scenario.runGitCommandExpectError("merge", "branch-a")
scenario.runGitCommandExpectError("merge", "branch-b")
// Test that our tools handle merge state gracefully
hasChangesResult := hasChanges(scenario.filename)
t.Logf("hasChanges during merge conflict: %v", hasChangesResult)
// This should handle the conflict state without crashing
if hasChangesResult {
t.Log("Correctly detected changes during merge conflict")
}
})
}
// TestAdvancedFileTypeScenarios tests different file types and edge cases
func TestAdvancedFileTypeScenarios(t *testing.T) {
t.Run("unicode and special characters", func(t *testing.T) {
scenario := createTestScenario(t, "unicode")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Test with Unicode content
unicodeContent := "Hello 世界\nBonjour 🌍\nHola Ωorld\n日本語テスト\n"
scenario.commitFile(scenario.filename, unicodeContent, "Unicode content")
// Modify with more Unicode
modifiedContent := "UPDATED 世界\nBonjour 🌍\nHola Ωorld\n日本語テスト更新\nEmoji test: 🚀🎉\n"
scenario.modifyFile(scenario.filename, modifiedContent)
// Test staging Unicode changes
err := stageLines(scenario.filename, []int{1, 4, 5})
if err != nil {
t.Fatal("Failed to stage Unicode content:", err)
}
// Verify staging worked with Unicode
stagedDiff := scenario.runGitCommand("diff", "--cached", scenario.filename)
if !strings.Contains(stagedDiff, "UPDATED 世界") {
t.Error("Unicode staging failed")
}
if !strings.Contains(stagedDiff, "🚀") {
t.Error("Emoji staging failed")
}
})
t.Run("large files with many changes", func(t *testing.T) {
if testing.Short() {
t.Skip("Skipping large file test in short mode")
}
scenario := createTestScenario(t, "large_file")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create very large file
var content strings.Builder
for i := 1; i <= 5000; i++ {
content.WriteString(fmt.Sprintf("Line %d: This is a long line with substantial content to make the file large %s\n",
i, strings.Repeat("x", 50)))
}
scenario.commitFile(scenario.filename, content.String(), "Large file")
// Modify scattered lines throughout
modifiedContent := content.String()
linesToModify := []int{1, 100, 500, 1000, 2500, 4000, 4999}
for _, lineNum := range linesToModify {
oldLine := fmt.Sprintf("Line %d:", lineNum)
newLine := fmt.Sprintf("MODIFIED Line %d:", lineNum)
modifiedContent = strings.Replace(modifiedContent, oldLine, newLine, 1)
}
scenario.modifyFile(scenario.filename, modifiedContent)
// Time the staging operation
start := time.Now()
err := stageLines(scenario.filename, []int{1, 500, 2500})
duration := time.Since(start)
if err != nil {
t.Fatal("Failed to stage large file:", err)
}
t.Logf("Staging large file (5000 lines) took: %v", duration)
// Verify correctness
stagedDiff := scenario.runGitCommand("diff", "--cached", scenario.filename)
if !strings.Contains(stagedDiff, "MODIFIED Line 1:") {
t.Error("Line 1 should be staged")
}
if !strings.Contains(stagedDiff, "MODIFIED Line 500:") {
t.Error("Line 500 should be staged")
}
if !strings.Contains(stagedDiff, "MODIFIED Line 2500:") {
t.Error("Line 2500 should be staged")
}
// Verify unstaged changes remain
unstagedDiff := scenario.runGitCommand("diff", scenario.filename)
if !strings.Contains(unstagedDiff, "MODIFIED Line 100:") {
t.Error("Line 100 should remain unstaged")
}
})
t.Run("binary files handling", func(t *testing.T) {
scenario := createTestScenario(t, "binary_files")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create a binary-like file
binaryContent := "This is text\x00\x01\x02\xff\xfe\x03Binary content here\n"
binaryFile := "binary.dat"
scenario.writeFile(binaryFile, binaryContent)
scenario.runGitCommand("add", binaryFile)
scenario.runGitCommand("commit", "-m", "Add binary file")
// Modify the binary file
modifiedBinaryContent := "This is MODIFIED text\x00\x01\x02\xff\xfe\x03Binary content updated\n"
scenario.modifyFile(binaryFile, modifiedBinaryContent)
// Test how our tools handle binary files
hasChangesResult := hasChanges(binaryFile)
t.Logf("hasChanges for binary file: %v", hasChangesResult)
// Staging binary files should either work or fail gracefully
err := stageLines(binaryFile, []int{1})
if err != nil {
t.Logf("stageLines failed for binary file (expected): %v", err)
} else {
t.Log("stageLines handled binary file successfully")
}
})
}
// TestAdvancedLineEndingScenarios tests different line ending scenarios
func TestAdvancedLineEndingScenarios(t *testing.T) {
t.Run("mixed line endings", func(t *testing.T) {
scenario := createTestScenario(t, "line_endings")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create file with mixed line endings (LF and CRLF)
mixedContent := "Line 1 with LF\nLine 2 with CRLF\r\nLine 3 with LF\nLine 4 with CRLF\r\n"
scenario.commitFile(scenario.filename, mixedContent, "Mixed line endings")
// Modify with consistent line endings
consistentContent := "MODIFIED Line 1 with LF\nMODIFIED Line 2 with LF\nMODIFIED Line 3 with LF\nLine 4 with CRLF\r\n"
scenario.modifyFile(scenario.filename, consistentContent)
// Test staging with mixed line endings
err := stageLines(scenario.filename, []int{1, 3})
if err != nil {
t.Fatal("Failed to stage mixed line endings:", err)
}
// Verify staging worked
stagedDiff := scenario.runGitCommand("diff", "--cached", scenario.filename)
if stagedDiff == "" {
t.Error("Expected staged changes for mixed line endings")
}
t.Logf("Mixed line endings handled successfully")
})
}
// TestAdvancedGitStateScenarios tests various git repository states
func TestAdvancedGitStateScenarios(t *testing.T) {
t.Run("detached HEAD state", func(t *testing.T) {
scenario := createTestScenario(t, "detached_head")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create commits
commit1 := scenario.commitFile(scenario.filename, "version 1\n", "Version 1")
scenario.commitFile(scenario.filename, "version 1\nversion 2\n", "Version 2")
// Detach HEAD to first commit
scenario.runGitCommand("checkout", commit1)
// Modify file in detached HEAD state
scenario.modifyFile(scenario.filename, "MODIFIED version 1\n")
// Test functionality in detached HEAD state
hasChangesResult := hasChanges(scenario.filename)
if !hasChangesResult {
t.Error("Should detect changes in detached HEAD state")
}
// Test staging in detached HEAD state
err := stageLines(scenario.filename, []int{1})
if err != nil {
t.Logf("stageLines in detached HEAD (may fail): %v", err)
} else {
t.Log("stageLines worked in detached HEAD state")
}
})
t.Run("shallow repository", func(t *testing.T) {
scenario := createTestScenario(t, "shallow_repo")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create several commits
for i := 1; i <= 5; i++ {
content := fmt.Sprintf("Version %d\n", i)
for j := 1; j < i; j++ {
content += fmt.Sprintf("Line %d from version %d\n", j, j)
}
scenario.commitFile(scenario.filename, content, fmt.Sprintf("Version %d", i))
}
// Make it a shallow repository (this simulates cloning with --depth)
// Note: We can't easily make the existing repo shallow, but we can test edge cases
// Modify current file
scenario.modifyFile(scenario.filename, "MODIFIED Version 5\nLine 1 from version 1\nLine 2 from version 2\nLine 3 from version 3\nLine 4 from version 4\n")
// Test functionality with deep history
err := stageLines(scenario.filename, []int{1})
if err != nil {
t.Fatal("Failed to stage in repository with history:", err)
}
t.Log("Deep history repository handled successfully")
})
t.Run("empty repository edge cases", func(t *testing.T) {
scenario := createTestScenario(t, "empty_repo")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Don't create any commits - test with completely empty repo
scenario.writeFile(scenario.filename, "First content ever\n")
// Test hasChanges on untracked file
hasChangesResult := hasChanges(scenario.filename)
t.Logf("hasChanges for untracked file in empty repo: %v", hasChangesResult)
// Add and test staging
scenario.runGitCommand("add", scenario.filename)
// Modify the staged file
scenario.modifyFile(scenario.filename, "MODIFIED first content\n")
// Test staging modifications to a file that's never been committed
err := stageLines(scenario.filename, []int{1})
if err != nil {
t.Logf("stageLines failed for never-committed file (expected): %v", err)
} else {
t.Log("stageLines handled never-committed file")
}
})
}
// TestAdvancedPerformanceScenarios tests performance with various challenging scenarios
func TestAdvancedPerformanceScenarios(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance tests in short mode")
}
t.Run("many small commits performance", func(t *testing.T) {
scenario := createTestScenario(t, "many_commits")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Create many small commits to build history
content := "initial line\n"
scenario.commitFile(scenario.filename, content, "Initial commit")
for i := 1; i <= 100; i++ {
content += fmt.Sprintf("line %d added\n", i)
scenario.commitFile(scenario.filename, content, fmt.Sprintf("Add line %d", i))
}
// Now modify and test performance
content += "FINAL MODIFICATION\n"
scenario.modifyFile(scenario.filename, content)
start := time.Now()
hasChangesResult := hasChanges(scenario.filename)
hasChangesDuration := time.Since(start)
start = time.Now()
err := stageLines(scenario.filename, []int{102}) // Stage the final line
stageDuration := time.Since(start)
if err != nil {
t.Fatal("Failed to stage in repo with many commits:", err)
}
if !hasChangesResult {
t.Error("Should detect changes in repo with many commits")
}
t.Logf("Performance with 100 commits - hasChanges: %v, stageLines: %v",
hasChangesDuration, stageDuration)
// Performance should still be reasonable
if hasChangesDuration > time.Second || stageDuration > 5*time.Second {
t.Logf("Performance warning: operations took longer than expected")
}
})
}
// TestAdvancedErrorHandling tests error handling in complex scenarios
func TestAdvancedErrorHandling(t *testing.T) {
t.Run("concurrent git operations", func(t *testing.T) {
scenario := createTestScenario(t, "concurrent_ops")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
scenario.commitFile(scenario.filename, "content\n", "Initial")
scenario.modifyFile(scenario.filename, "modified content\n")
// Test that our operations are safe when git index might be locked
// This is hard to test directly, but we can at least verify they complete
err1 := stageLines(scenario.filename, []int{1})
if err1 != nil {
t.Fatal("First staging failed:", err1)
}
// Immediate second operation
err2 := stageLines(scenario.filename, []int{1})
if err2 != nil {
t.Logf("Second staging operation result: %v", err2)
}
t.Log("Concurrent operation handling completed")
})
t.Run("file system permission issues", func(t *testing.T) {
scenario := createTestScenario(t, "permissions")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
scenario.commitFile(scenario.filename, "content\n", "Initial")
scenario.modifyFile(scenario.filename, "modified\n")
// Make git directory read-only temporarily to test error handling
gitDir := filepath.Join(scenario.dir, ".git")
originalMode, err := os.Stat(gitDir)
if err != nil {
t.Skip("Could not stat .git directory")
}
// Try to make operations fail gracefully
err = stageLines(scenario.filename, []int{1})
if err != nil {
t.Logf("stageLines handled permission issues: %v", err)
}
// Restore permissions
os.Chmod(gitDir, originalMode.Mode())
t.Log("Permission error handling test completed")
})
}
// TestAdvancedRealWorldWorkflows tests complete real-world workflows
func TestAdvancedRealWorldWorkflows(t *testing.T) {
t.Run("full development workflow", func(t *testing.T) {
scenario := createTestScenario(t, "full_workflow")
defer scenario.cleanup()
restoreDir := scenario.changeToScenarioDir()
defer restoreDir()
// Simulate a real development workflow
// 1. Initial project setup
scenario.commitFile("README.md", "# My Project\n\nThis is a sample project.\n", "Initial README")
scenario.commitFile("main.go", "package main\n\nfunc main() {\n println(\"Hello\")\n}\n", "Initial main")
scenario.commitFile("config.json", `{"debug": false, "port": 8080}`, "Initial config")
// 2. Feature development - modify multiple files
scenario.modifyFile("main.go", "package main\n\nimport \"fmt\"\n\nfunc main() {\n fmt.Println(\"Hello World\")\n fmt.Println(\"Debug mode\")\n}\n")
scenario.modifyFile("README.md", "# My Project\n\nThis is a sample project with new features.\n\n## Usage\n\nRun with: go run main.go\n")
scenario.modifyFile("config.json", `{"debug": true, "port": 9090, "timeout": 30}`)
// 3. Selective staging - stage bug fix but not debug code
err := stageLines("main.go", []int{5}) // Stage "Hello World" change but not debug line
if err != nil {
t.Fatal("Failed to stage main.go:", err)
}
err = stageLines("config.json", []int{1}) // Stage the JSON changes (this is tricky with JSON)
if err != nil {
t.Logf("JSON staging failed (expected for complex JSON): %v", err)
}
// 4. Create fixup commit
mainCommitHash := scenario.runGitCommand("log", "--format=%H", "-n", "1", "--grep=Initial main")
if mainCommitHash != "" {
err = createFixupCommit(strings.TrimSpace(mainCommitHash))
if err != nil {
t.Fatal("Failed to create fixup commit:", err)
}
}
// 5. Verify workflow state
status := scenario.runGitCommand("status", "--porcelain")
log := scenario.runGitCommand("log", "--oneline", "-3")
t.Logf("Final status: %s", status)
t.Logf("Recent commits: %s", log)
// Should have staged changes, unstaged changes, and a fixup commit
if !strings.Contains(log, "fixup!") {
t.Error("Expected fixup commit in history")
}
t.Log("Full development workflow completed successfully")
})
}