From 77881a9c7d24bb11bc74abff35d94397fd4ccb67 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 7 Aug 2022 22:09:39 +1000 Subject: add new integration test pattern --- .gitignore | 10 +- main.go | 5 + pkg/gui/assert.go | 88 ++++ pkg/gui/gui.go | 31 +- pkg/gui/gui_test.go | 34 +- pkg/gui/input.go | 93 ++++ pkg/gui/old_gui_test.go | 76 +++ pkg/gui/recording.go | 74 --- pkg/gui/test_mode.go | 63 +++ pkg/integration/env.go | 46 ++ pkg/integration/integration.go | 519 +++---------------- pkg/integration/integration_old.go | 564 +++++++++++++++++++++ .../integration_tests/branch/suggestions.go | 40 ++ pkg/integration/integration_tests/commit/commit.go | 32 ++ .../integration_tests/commit/new_branch.go | 38 ++ pkg/integration/integration_tests/tests.go | 16 + pkg/integration/recording.go | 61 +++ pkg/integration/shell.go | 53 ++ pkg/integration/types/types.go | 152 ++++++ pkg/utils/utils.go | 7 + .../expected/repo/.git_keep/COMMIT_EDITMSG | 1 - .../expected/repo/.git_keep/FETCH_HEAD | 0 .../branchSuggestions/expected/repo/.git_keep/HEAD | 1 - .../expected/repo/.git_keep/config | 10 - .../expected/repo/.git_keep/description | 1 - .../expected/repo/.git_keep/index | Bin 137 -> 0 bytes .../expected/repo/.git_keep/info/exclude | 7 - .../expected/repo/.git_keep/logs/HEAD | 8 - .../expected/repo/.git_keep/logs/refs/heads/master | 1 - .../repo/.git_keep/logs/refs/heads/new-branch | 1 - .../repo/.git_keep/logs/refs/heads/new-branch-2 | 1 - .../repo/.git_keep/logs/refs/heads/new-branch-3 | 1 - .../repo/.git_keep/logs/refs/heads/old-branch | 1 - .../repo/.git_keep/logs/refs/heads/old-branch-2 | 1 - .../repo/.git_keep/logs/refs/heads/old-branch-3 | 1 - .../1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 | Bin 50 -> 0 bytes .../38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da | Bin 21 -> 0 bytes .../75/e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 | 2 - .../expected/repo/.git_keep/refs/heads/master | 1 - .../expected/repo/.git_keep/refs/heads/new-branch | 1 - .../repo/.git_keep/refs/heads/new-branch-2 | 1 - .../repo/.git_keep/refs/heads/new-branch-3 | 1 - .../expected/repo/.git_keep/refs/heads/old-branch | 1 - .../repo/.git_keep/refs/heads/old-branch-2 | 1 - .../repo/.git_keep/refs/heads/old-branch-3 | 1 - .../branchSuggestions/expected/repo/file0 | 1 - test/integration/branchSuggestions/recording.json | 1 - test/integration/branchSuggestions/setup.sh | 21 - test/integration/branchSuggestions/test.json | 1 - .../commit/expected/repo/.git_keep/COMMIT_EDITMSG | 1 - .../commit/expected/repo/.git_keep/FETCH_HEAD | 0 .../commit/expected/repo/.git_keep/HEAD | 1 - .../commit/expected/repo/.git_keep/config | 10 - .../commit/expected/repo/.git_keep/description | 1 - .../commit/expected/repo/.git_keep/index | Bin 425 -> 0 bytes .../commit/expected/repo/.git_keep/info/exclude | 7 - .../commit/expected/repo/.git_keep/logs/HEAD | 5 - .../expected/repo/.git_keep/logs/refs/heads/master | 5 - .../0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 | Bin 52 -> 0 bytes .../14/40bc6cc888a09dca2329d1060eec6de78d9d21 | Bin 148 -> 0 bytes .../18/0cf8328022becee9aaa2577a8f84ea2b9f3827 | Bin 21 -> 0 bytes .../2b/173c861df433fa43ffad13f80c8b312c5c8bce | Bin 103 -> 0 bytes .../2f/6174050380438f14b16658a356e762435ca591 | Bin 128 -> 0 bytes .../30/a1ca3481fdec3245b02aeacfb72ddfe2a433be | Bin 154 -> 0 bytes .../3d/f3d8761bc0f0828596b11845aeac175b7b7393 | 3 - .../4b/a4f1ed711a9081fab21bc222469aa5176a01f8 | Bin 149 -> 0 bytes .../4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f | Bin 21 -> 0 bytes .../a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 | Bin 21 -> 0 bytes .../a7/341a59f0ddeef969e69fb6368266d22b0f2416 | Bin 77 -> 0 bytes .../a7/d53cc21fd53100f955377be379423b0e386274 | Bin 150 -> 0 bytes .../d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 | Bin 21 -> 0 bytes .../df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b | Bin 21 -> 0 bytes .../e7/560e2cd4783a261ad32496cefed2d9f69a46e7 | Bin 145 -> 0 bytes .../expected/repo/.git_keep/refs/heads/master | 1 - test/integration/commit/expected/repo/myfile1 | 1 - test/integration/commit/expected/repo/myfile2 | 1 - test/integration/commit/expected/repo/myfile3 | 1 - test/integration/commit/expected/repo/myfile4 | 1 - test/integration/commit/expected/repo/myfile5 | 1 - test/integration/commit/recording.json | 1 - test/integration/commit/setup.sh | 24 - test/integration/commit/test.json | 1 - .../expected/repo/.git_keep/COMMIT_EDITMSG | 1 - .../expected/repo/.git_keep/FETCH_HEAD | 0 .../commitsNewBranch/expected/repo/.git_keep/HEAD | 1 - .../expected/repo/.git_keep/config | 10 - .../expected/repo/.git_keep/description | 1 - .../commitsNewBranch/expected/repo/.git_keep/index | Bin 209 -> 0 bytes .../expected/repo/.git_keep/info/exclude | 7 - .../expected/repo/.git_keep/logs/HEAD | 4 - .../expected/repo/.git_keep/logs/refs/heads/lol | 1 - .../expected/repo/.git_keep/logs/refs/heads/master | 3 - .../00/29f9bf66e346d47ede6a501abb5b82bee60096 | Bin 148 -> 0 bytes .../18/0cf8328022becee9aaa2577a8f84ea2b9f3827 | Bin 21 -> 0 bytes .../1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 | Bin 50 -> 0 bytes .../38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da | Bin 21 -> 0 bytes .../99/01fd9b7766be600bed07f55f1794a759527a98 | Bin 118 -> 0 bytes .../9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c | Bin 101 -> 0 bytes .../a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 | Bin 21 -> 0 bytes .../d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 | 2 - .../e1/cb250774fb8606d33062518d0ae03831130249 | Bin 148 -> 0 bytes .../expected/repo/.git_keep/refs/heads/lol | 1 - .../expected/repo/.git_keep/refs/heads/master | 1 - .../commitsNewBranch/expected/repo/file0 | 1 - .../commitsNewBranch/expected/repo/file1 | 1 - test/integration/commitsNewBranch/recording.json | 1 - test/integration/commitsNewBranch/setup.sh | 22 - test/integration/commitsNewBranch/test.json | 1 - .../expected/repo/.git_keep/COMMIT_EDITMSG | 1 + .../suggestions/expected/repo/.git_keep/FETCH_HEAD | 0 .../suggestions/expected/repo/.git_keep/HEAD | 1 + .../suggestions/expected/repo/.git_keep/config | 10 + .../expected/repo/.git_keep/description | 1 + .../suggestions/expected/repo/.git_keep/index | Bin 0 -> 65 bytes .../expected/repo/.git_keep/info/exclude | 7 + .../suggestions/expected/repo/.git_keep/logs/HEAD | 8 + .../.git_keep/logs/refs/heads/branch-to-checkout | 1 + .../expected/repo/.git_keep/logs/refs/heads/master | 1 + .../repo/.git_keep/logs/refs/heads/new-branch | 1 + .../repo/.git_keep/logs/refs/heads/new-branch-2 | 1 + .../repo/.git_keep/logs/refs/heads/new-branch-3 | 1 + .../.git_keep/logs/refs/heads/other-new-branch-2 | 1 + .../.git_keep/logs/refs/heads/other-new-branch-3 | 1 + .../16/82dc1949e1937af44b5270fec5c1ac9256c6a1 | Bin 0 -> 125 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 0 -> 15 bytes .../repo/.git_keep/refs/heads/branch-to-checkout | 1 + .../expected/repo/.git_keep/refs/heads/master | 1 + .../expected/repo/.git_keep/refs/heads/new-branch | 1 + .../repo/.git_keep/refs/heads/new-branch-2 | 1 + .../repo/.git_keep/refs/heads/new-branch-3 | 1 + .../repo/.git_keep/refs/heads/other-new-branch-2 | 1 + .../repo/.git_keep/refs/heads/other-new-branch-3 | 1 + .../commit/expected/repo/.git_keep/COMMIT_EDITMSG | 1 + .../commit/expected/repo/.git_keep/FETCH_HEAD | 0 .../commit/commit/expected/repo/.git_keep/HEAD | 1 + .../commit/commit/expected/repo/.git_keep/config | 10 + .../commit/expected/repo/.git_keep/description | 1 + .../commit/commit/expected/repo/.git_keep/index | Bin 0 -> 209 bytes .../commit/expected/repo/.git_keep/info/exclude | 7 + .../commit/expected/repo/.git_keep/logs/HEAD | 1 + .../expected/repo/.git_keep/logs/refs/heads/master | 1 + .../3a/e2df795236e3c84cb1faa242d3268838603515 | Bin 0 -> 76 bytes .../46/0150760ff1f381c3f5769b919cb73107c5871a | Bin 0 -> 125 bytes .../97/04090f88911a4083ef7d5907e38b9f45e43b16 | Bin 0 -> 31 bytes .../ad/a5661567ddf0a64f589cad3cd0cffd7e79af99 | Bin 0 -> 30 bytes .../expected/repo/.git_keep/refs/heads/master | 1 + .../commit/commit/expected/repo/myfile | 1 + .../commit/commit/expected/repo/myfile2 | 1 + .../expected/repo/.git_keep/COMMIT_EDITMSG | 1 + .../new_branch/expected/repo/.git_keep/FETCH_HEAD | 0 .../commit/new_branch/expected/repo/.git_keep/HEAD | 1 + .../new_branch/expected/repo/.git_keep/config | 10 + .../new_branch/expected/repo/.git_keep/description | 1 + .../new_branch/expected/repo/.git_keep/index | Bin 0 -> 65 bytes .../expected/repo/.git_keep/info/exclude | 7 + .../new_branch/expected/repo/.git_keep/logs/HEAD | 4 + .../expected/repo/.git_keep/logs/refs/heads/master | 3 + .../repo/.git_keep/logs/refs/heads/my-branch-name | 1 + .../47/0038e1336649b2965305f9f6a82501a836810e | Bin 0 -> 118 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 0 -> 15 bytes .../62/a60693a2e154e745ee353f67a05156d0532c23 | Bin 0 -> 148 bytes .../c8/bec8f2b323cbb476e708bd10c145ea7cc9f726 | 2 + .../expected/repo/.git_keep/refs/heads/master | 1 + .../repo/.git_keep/refs/heads/my-branch-name | 1 + test/runner/main.go | 2 + test/runner_new/main.go | 76 +++ vendor/github.com/jesseduffield/gocui/gui.go | 16 +- .../github.com/jesseduffield/gocui/tcell_driver.go | 6 +- 168 files changed, 1618 insertions(+), 774 deletions(-) create mode 100644 pkg/gui/assert.go create mode 100644 pkg/gui/input.go create mode 100644 pkg/gui/old_gui_test.go delete mode 100644 pkg/gui/recording.go create mode 100644 pkg/gui/test_mode.go create mode 100644 pkg/integration/env.go create mode 100644 pkg/integration/integration_old.go create mode 100644 pkg/integration/integration_tests/branch/suggestions.go create mode 100644 pkg/integration/integration_tests/commit/commit.go create mode 100644 pkg/integration/integration_tests/commit/new_branch.go create mode 100644 pkg/integration/integration_tests/tests.go create mode 100644 pkg/integration/recording.go create mode 100644 pkg/integration/shell.go create mode 100644 pkg/integration/types/types.go delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/COMMIT_EDITMSG delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/FETCH_HEAD delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/HEAD delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/config delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/description delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/index delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/info/exclude delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/HEAD delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/master delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-2 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-3 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/objects/75/e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/master delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-2 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-3 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-2 delete mode 100644 test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-3 delete mode 100644 test/integration/branchSuggestions/expected/repo/file0 delete mode 100644 test/integration/branchSuggestions/recording.json delete mode 100644 test/integration/branchSuggestions/setup.sh delete mode 100644 test/integration/branchSuggestions/test.json delete mode 100644 test/integration/commit/expected/repo/.git_keep/COMMIT_EDITMSG delete mode 100644 test/integration/commit/expected/repo/.git_keep/FETCH_HEAD delete mode 100644 test/integration/commit/expected/repo/.git_keep/HEAD delete mode 100644 test/integration/commit/expected/repo/.git_keep/config delete mode 100644 test/integration/commit/expected/repo/.git_keep/description delete mode 100644 test/integration/commit/expected/repo/.git_keep/index delete mode 100644 test/integration/commit/expected/repo/.git_keep/info/exclude delete mode 100644 test/integration/commit/expected/repo/.git_keep/logs/HEAD delete mode 100644 test/integration/commit/expected/repo/.git_keep/logs/refs/heads/master delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/14/40bc6cc888a09dca2329d1060eec6de78d9d21 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/30/a1ca3481fdec3245b02aeacfb72ddfe2a433be delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/3d/f3d8761bc0f0828596b11845aeac175b7b7393 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/4b/a4f1ed711a9081fab21bc222469aa5176a01f8 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/a7/d53cc21fd53100f955377be379423b0e386274 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b delete mode 100644 test/integration/commit/expected/repo/.git_keep/objects/e7/560e2cd4783a261ad32496cefed2d9f69a46e7 delete mode 100644 test/integration/commit/expected/repo/.git_keep/refs/heads/master delete mode 100644 test/integration/commit/expected/repo/myfile1 delete mode 100644 test/integration/commit/expected/repo/myfile2 delete mode 100644 test/integration/commit/expected/repo/myfile3 delete mode 100644 test/integration/commit/expected/repo/myfile4 delete mode 100644 test/integration/commit/expected/repo/myfile5 delete mode 100644 test/integration/commit/recording.json delete mode 100644 test/integration/commit/setup.sh delete mode 100644 test/integration/commit/test.json delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/COMMIT_EDITMSG delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/FETCH_HEAD delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/HEAD delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/config delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/description delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/index delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/info/exclude delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/logs/HEAD delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/lol delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/master delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/00/29f9bf66e346d47ede6a501abb5b82bee60096 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/99/01fd9b7766be600bed07f55f1794a759527a98 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/objects/e1/cb250774fb8606d33062518d0ae03831130249 delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/lol delete mode 100644 test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/master delete mode 100644 test/integration/commitsNewBranch/expected/repo/file0 delete mode 100644 test/integration/commitsNewBranch/expected/repo/file1 delete mode 100644 test/integration/commitsNewBranch/recording.json delete mode 100644 test/integration/commitsNewBranch/setup.sh delete mode 100644 test/integration/commitsNewBranch/test.json create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/COMMIT_EDITMSG create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/FETCH_HEAD create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/HEAD create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/config create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/description create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/index create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/info/exclude create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/HEAD create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/branch-to-checkout create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/master create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-2 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-3 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/16/82dc1949e1937af44b5270fec5c1ac9256c6a1 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/branch-to-checkout create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/master create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-2 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-3 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-2 create mode 100644 test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-3 create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/COMMIT_EDITMSG create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/FETCH_HEAD create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/HEAD create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/config create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/description create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/index create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/info/exclude create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/objects/3a/e2df795236e3c84cb1faa242d3268838603515 create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/objects/46/0150760ff1f381c3f5769b919cb73107c5871a create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/objects/97/04090f88911a4083ef7d5907e38b9f45e43b16 create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/objects/ad/a5661567ddf0a64f589cad3cd0cffd7e79af99 create mode 100644 test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master create mode 100644 test/integration_new/commit/commit/expected/repo/myfile create mode 100644 test/integration_new/commit/commit/expected/repo/myfile2 create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/COMMIT_EDITMSG create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/FETCH_HEAD create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/HEAD create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/config create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/description create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/index create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/info/exclude create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/47/0038e1336649b2965305f9f6a82501a836810e create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/62/a60693a2e154e745ee353f67a05156d0532c23 create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/c8/bec8f2b323cbb476e708bd10c145ea7cc9f726 create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/master create mode 100644 test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/my-branch-name create mode 100644 test/runner_new/main.go diff --git a/.gitignore b/.gitignore index f5ba66f88..e9ed453a2 100644 --- a/.gitignore +++ b/.gitignore @@ -33,11 +33,19 @@ lazygit.exe !.gitmodules_keep test/git_server/data + +# we'll scrap these lines once we've fully moved over to the new integration test approach test/integration/*/actual/ test/integration/*/used_config/ # these sample hooks waste too much space test/integration/*/expected/**/hooks/ test/integration/*/expected_remote/**/hooks/ +test/integration_new/**/actual/ +test/integration_new/**/used_config/ +# these sample hooks waste too much space +test/integration_new/**/expected/**/hooks/ +test/integration_new/**/expected_remote/**/hooks/ + oryxBuildBinary -__debug_bin \ No newline at end of file +__debug_bin diff --git a/main.go b/main.go index e21751c27..651d2be09 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/integration" "github.com/jesseduffield/lazygit/pkg/logs" "github.com/jesseduffield/lazygit/pkg/utils" yaml "github.com/jesseduffield/yaml" @@ -150,6 +151,10 @@ func main() { log.Fatal(err.Error()) } + if test, ok := integration.CurrentIntegrationTest(); ok { + test.SetupConfig(appConfig) + } + common, err := app.NewCommon(appConfig) if err != nil { log.Fatal(err) diff --git a/pkg/gui/assert.go b/pkg/gui/assert.go new file mode 100644 index 000000000..43ecc26d8 --- /dev/null +++ b/pkg/gui/assert.go @@ -0,0 +1,88 @@ +package gui + +import ( + "fmt" + "time" + + "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +type AssertImpl struct { + gui *Gui +} + +var _ types.Assert = &AssertImpl{} + +func (self *AssertImpl) WorkingTreeFileCount(expectedCount int) { + self.assertWithRetries(func() (bool, string) { + actualCount := len(self.gui.State.Model.Files) + + return actualCount == expectedCount, fmt.Sprintf( + "Expected %d changed working tree files, but got %d", + expectedCount, actualCount, + ) + }) +} + +func (self *AssertImpl) CommitCount(expectedCount int) { + self.assertWithRetries(func() (bool, string) { + actualCount := len(self.gui.State.Model.Commits) + + return actualCount == expectedCount, fmt.Sprintf( + "Expected %d commits present, but got %d", + expectedCount, actualCount, + ) + }) +} + +func (self *AssertImpl) HeadCommitMessage(expectedMessage string) { + self.assertWithRetries(func() (bool, string) { + if len(self.gui.State.Model.Commits) == 0 { + return false, "Expected at least one commit to be present" + } + + headCommit := self.gui.State.Model.Commits[0] + if headCommit.Name != expectedMessage { + return false, fmt.Sprintf( + "Expected commit message to be '%s', but got '%s'", + expectedMessage, headCommit.Name, + ) + } + + return true, "" + }) +} + +func (self *AssertImpl) CurrentViewName(expectedViewName string) { + self.assertWithRetries(func() (bool, string) { + actual := self.gui.currentViewName() + return actual == expectedViewName, fmt.Sprintf("Expected current view name to be '%s', but got '%s'", expectedViewName, actual) + }) +} + +func (self *AssertImpl) CurrentBranchName(expectedViewName string) { + self.assertWithRetries(func() (bool, string) { + actual := self.gui.helpers.Refs.GetCheckedOutRef().Name + return actual == expectedViewName, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expectedViewName, actual) + }) +} + +func (self *AssertImpl) assertWithRetries(test func() (bool, string)) { + waitTimes := []int{0, 100, 200, 400, 800, 1600} + + var message string + for _, waitTime := range waitTimes { + time.Sleep(time.Duration(waitTime) * time.Millisecond) + + var ok bool + ok, message = test() + if ok { + return + } + } + + self.gui.g.Close() + // need to give the gui time to close + time.Sleep(time.Millisecond * 100) + panic(message) +} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bb5821a57..f7c8926f5 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -31,6 +31,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/integration" "github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/updates" @@ -418,12 +419,14 @@ var RuneReplacements = map[rune]string{ } func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) { - recordEvents := recordingEvents() + recordEvents := integration.RecordingEvents() playMode := gocui.NORMAL if recordEvents { playMode = gocui.RECORDING - } else if replaying() { + } else if integration.Replaying() { playMode = gocui.REPLAYING + } else if integration.IntegrationTestName() != "" { + playMode = gocui.REPLAYING_NEW } g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless, RuneReplacements) @@ -475,7 +478,7 @@ func (gui *Gui) viewTabMap() map[string][]context.TabView { // Run: setup the gui with keybindings and start the mainloop func (gui *Gui) Run(startArgs types.StartArgs) error { - g, err := gui.initGocui(headless()) + g, err := gui.initGocui(integration.Headless()) if err != nil { return err } @@ -490,23 +493,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error { }) deadlock.Opts.Disable = !gui.Debug - if replaying() { - gui.g.RecordingConfig = gocui.RecordingConfig{ - Speed: getRecordingSpeed(), - Leeway: 100, - } - - var err error - gui.g.Recording, err = gui.loadRecording() - if err != nil { - return err - } - - go utils.Safe(func() { - time.Sleep(time.Second * 40) - log.Fatal("40 seconds is up, lazygit recording took too long to complete") - }) - } + gui.handleTestMode() gui.g.OnSearchEscape = gui.onSearchEscape if err := gui.Config.ReloadUserConfig(); err != nil { @@ -593,7 +580,7 @@ func (gui *Gui) RunAndHandleError(startArgs types.StartArgs) error { } } - if err := gui.saveRecording(gui.g.Recording); err != nil { + if err := integration.SaveRecording(gui.g.Recording); err != nil { return err } @@ -627,7 +614,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool, gui.Mutexes.SubprocessMutex.Lock() defer gui.Mutexes.SubprocessMutex.Unlock() - if replaying() { + if integration.Replaying() { // we do not yet support running subprocesses within integration tests. So if // we're replaying an integration test and we're inside this method, something // has gone wrong, so we should fail diff --git a/pkg/gui/gui_test.go b/pkg/gui/gui_test.go index d2345d5d0..2565392e6 100644 --- a/pkg/gui/gui_test.go +++ b/pkg/gui/gui_test.go @@ -3,6 +3,9 @@ package gui +// this is the new way of running tests. See pkg/integration/integration_tests/commit.go +// for an example + import ( "fmt" "io" @@ -14,60 +17,37 @@ import ( "github.com/creack/pty" "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/stretchr/testify/assert" ) -// This file is quite similar to integration/main.go. The main difference is that this file is -// run via `go test` whereas the other is run via `test/lazyintegration/main.go` which provides -// a convenient gui wrapper around our integration tests. The `go test` approach is better -// for CI and for running locally in the background to ensure you haven't broken -// anything while making changes. If you want to visually see what's happening when a test is run, -// you'll need to take the other approach -// -// As for this file, to run an integration test, e.g. for test 'commit', go: -// go test pkg/gui/gui_test.go -run /commit -// -// To update a snapshot for an integration test, pass UPDATE_SNAPSHOTS=true -// UPDATE_SNAPSHOTS=true go test pkg/gui/gui_test.go -run /commit -// -// integration tests are run in test/integration//actual and the final test does -// not clean up that directory so you can cd into it to see for yourself what -// happened when a test fails. -// -// To override speed, pass e.g. `SPEED=1` as an env var. Otherwise we start each test -// at a high speed and then drop down to lower speeds upon each failure until finally -// trying at the original playback speed (speed 1). A speed of 2 represents twice the -// original playback speed. Speed may be a decimal. - func Test(t *testing.T) { if testing.Short() { t.Skip("Skipping integration tests in short mode") } mode := integration.GetModeFromEnv() - speedEnv := os.Getenv("SPEED") includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) testNumber := 0 - err := integration.RunTests( + err := integration.RunTestsNew( t.Logf, runCmdHeadless, - func(test *integration.Test, f func(*testing.T) error) { + func(test types.Test, f func(*testing.T) error) { defer func() { testNumber += 1 }() if testNumber%parallelTotal != parallelIndex { return } - t.Run(test.Name, func(t *testing.T) { + t.Run(test.Name(), func(t *testing.T) { err := f(t) assert.NoError(t, err) }) }, mode, - speedEnv, func(t *testing.T, expected string, actual string, prefix string) { t.Helper() assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) diff --git a/pkg/gui/input.go b/pkg/gui/input.go new file mode 100644 index 000000000..c6424c077 --- /dev/null +++ b/pkg/gui/input.go @@ -0,0 +1,93 @@ +package gui + +import ( + "time" + + "github.com/gdamore/tcell/v2" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/keybindings" + "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +type InputImpl struct { + g *gocui.Gui + keys config.KeybindingConfig +} + +var _ types.Input = &InputImpl{} + +func (self *InputImpl) PushKeys(keyStrs ...string) { + for _, keyStr := range keyStrs { + self.pushKey(keyStr) + } +} + +func (self *InputImpl) pushKey(keyStr string) { + key := keybindings.GetKey(keyStr) + + var r rune + var tcellKey tcell.Key + switch v := key.(type) { + case rune: + r = v + tcellKey = tcell.KeyRune + case gocui.Key: + tcellKey = tcell.Key(v) + } + + self.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper( + tcell.NewEventKey(tcellKey, r, tcell.ModNone), + 0, + ) +} + +func (self *InputImpl) SwitchToStatusWindow() { + self.pushKey(self.keys.Universal.JumpToBlock[0]) +} + +func (self *InputImpl) SwitchToFilesWindow() { + self.pushKey(self.keys.Universal.JumpToBlock[1]) +} + +func (self *InputImpl) SwitchToBranchesWindow() { + self.pushKey(self.keys.Universal.JumpToBlock[2]) +} + +func (self *InputImpl) SwitchToCommitsWindow() { + self.pushKey(self.keys.Universal.JumpToBlock[3]) +} + +func (self *InputImpl) SwitchToStashWindow() { + self.pushKey(self.keys.Universal.JumpToBlock[4]) +} + +func (self *InputImpl) Type(content string) { + for _, char := range content { + self.pushKey(string(char)) + } +} + +func (self *InputImpl) Confirm() { + self.pushKey(self.keys.Universal.Confirm) +} + +func (self *InputImpl) Cancel() { + self.pushKey(self.keys.Universal.Return) +} + +func (self *InputImpl) Select() { + self.pushKey(self.keys.Universal.Select) +} + +func (self *InputImpl) NextItem() { + self.pushKey(self.keys.Universal.NextItem) +} + +func (self *InputImpl) PreviousItem() { + self.pushKey(self.keys.Universal.PrevItem) +} + +func (self *InputImpl) Wait(milliseconds int) { + time.Sleep(time.Duration(milliseconds) * time.Millisecond) +} diff --git a/pkg/gui/old_gui_test.go b/pkg/gui/old_gui_test.go new file mode 100644 index 000000000..12e33432d --- /dev/null +++ b/pkg/gui/old_gui_test.go @@ -0,0 +1,76 @@ +//go:build !windows +// +build !windows + +package gui + +import ( + "fmt" + "os" + "testing" + + "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/stretchr/testify/assert" +) + +// Deprecated: this is the old way of running tests. See pkg/gui/gui_test.go for the new way. + +// This file is quite similar to integration/main.go. The main difference is that this file is +// run via `go test` whereas the other is run via `test/lazyintegration/main.go` which provides +// a convenient gui wrapper around our integration tests. The `go test` approach is better +// for CI and for running locally in the background to ensure you haven't broken +// anything while making changes. If you want to visually see what's happening when a test is run, +// you'll need to take the other approach +// +// As for this file, to run an integration test, e.g. for test 'commit', go: +// go test pkg/gui/old_gui_test.go -run /commit +// +// To update a snapshot for an integration test, pass UPDATE_SNAPSHOTS=true +// UPDATE_SNAPSHOTS=true go test pkg/gui/old_gui_test.go -run /commit +// +// integration tests are run in test/integration//actual and the final test does +// not clean up that directory so you can cd into it to see for yourself what +// happened when a test fails. +// +// To override speed, pass e.g. `SPEED=1` as an env var. Otherwise we start each test +// at a high speed and then drop down to lower speeds upon each failure until finally +// trying at the original playback speed (speed 1). A speed of 2 represents twice the +// original playback speed. Speed may be a decimal. + +func TestOld(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration tests in short mode") + } + + mode := integration.GetModeFromEnv() + speedEnv := os.Getenv("SPEED") + includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" + + parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) + parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) + testNumber := 0 + + err := integration.RunTests( + t.Logf, + runCmdHeadless, + func(test *integration.Test, f func(*testing.T) error) { + defer func() { testNumber += 1 }() + if testNumber%parallelTotal != parallelIndex { + return + } + + t.Run(test.Name, func(t *testing.T) { + err := f(t) + assert.NoError(t, err) + }) + }, + mode, + speedEnv, + func(t *testing.T, expected string, actual string, prefix string) { + t.Helper() + assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) + }, + includeSkipped, + ) + + assert.NoError(t, err) +} diff --git a/pkg/gui/recording.go b/pkg/gui/recording.go deleted file mode 100644 index 9edd50f08..000000000 --- a/pkg/gui/recording.go +++ /dev/null @@ -1,74 +0,0 @@ -package gui - -import ( - "encoding/json" - "io/ioutil" - "log" - "os" - "strconv" - - "github.com/jesseduffield/gocui" -) - -func recordingEvents() bool { - return recordEventsTo() != "" -} - -func recordEventsTo() string { - return os.Getenv("RECORD_EVENTS_TO") -} - -func replaying() bool { - return os.Getenv("REPLAY_EVENTS_FROM") != "" -} - -func headless() bool { - return os.Getenv("HEADLESS") != "" -} - -func getRecordingSpeed() float64 { - // humans are slow so this speeds things up. - speed := 1.0 - envReplaySpeed := os.Getenv("SPEED") - if envReplaySpeed != "" { - var err error - speed, err = strconv.ParseFloat(envReplaySpeed, 64) - if err != nil { - log.Fatal(err) - } - } - return speed -} - -func (gui *Gui) loadRecording() (*gocui.Recording, error) { - path := os.Getenv("REPLAY_EVENTS_FROM") - - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - recording := &gocui.Recording{} - - err = json.Unmarshal(data, &recording) - if err != nil { - return nil, err - } - - return recording, nil -} - -func (gui *Gui) saveRecording(recording *gocui.Recording) error { - if !recordingEvents() { - return nil - } - - jsonEvents, err := json.Marshal(recording) - if err != nil { - return err - } - - path := recordEventsTo() - - return ioutil.WriteFile(path, jsonEvents, 0o600) -} diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go new file mode 100644 index 000000000..88e42e440 --- /dev/null +++ b/pkg/gui/test_mode.go @@ -0,0 +1,63 @@ +package gui + +import ( + "fmt" + "log" + "time" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func (gui *Gui) handleTestMode() { + if integration.PlayingIntegrationTest() { + test, ok := integration.CurrentIntegrationTest() + + if !ok { + panic(fmt.Sprintf("test %s not found", integration.IntegrationTestName())) + } + + go func() { + time.Sleep(time.Millisecond * 100) + + test.Run( + &integration.ShellImpl{}, + &InputImpl{g: gui.g, keys: gui.Config.GetUserConfig().Keybinding}, + &AssertImpl{gui: gui}, + gui.c.UserConfig.Keybinding, + ) + + gui.g.Update(func(*gocui.Gui) error { + return gocui.ErrQuit + }) + + time.Sleep(time.Second * 1) + + log.Fatal("gocui should have already exited") + }() + + go utils.Safe(func() { + time.Sleep(time.Second * 40) + log.Fatal("40 seconds is up, lazygit recording took too long to complete") + }) + } + + if integration.Replaying() { + gui.g.RecordingConfig = gocui.RecordingConfig{ + Speed: integration.GetRecordingSpeed(), + Leeway: 100, + } + + var err error + gui.g.Recording, err = integration.LoadRecording() + if err != nil { + panic(err) + } + + go utils.Safe(func() { + time.Sleep(time.Second * 40) + log.Fatal("40 seconds is up, lazygit recording took too long to complete") + }) + } +} diff --git a/pkg/integration/env.go b/pkg/integration/env.go new file mode 100644 index 000000000..7cdc72267 --- /dev/null +++ b/pkg/integration/env.go @@ -0,0 +1,46 @@ +package integration + +import ( + "os" + + "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +func Headless() bool { + return os.Getenv("HEADLESS") != "" +} + +// NEW integration test format stuff + +func IntegrationTestName() string { + return os.Getenv("LAZYGIT_TEST_NAME") +} + +func CurrentIntegrationTest() (types.Test, bool) { + if !PlayingIntegrationTest() { + return nil, false + } + + return slices.Find(Tests, func(test types.Test) bool { + return test.Name() == IntegrationTestName() + }) +} + +func PlayingIntegrationTest() bool { + return IntegrationTestName() != "" +} + +// OLD integration test format stuff + +func Replaying() bool { + return os.Getenv("REPLAY_EVENTS_FROM") != "" +} + +func RecordingEvents() bool { + return recordEventsTo() != "" +} + +func recordEventsTo() string { + return os.Getenv("RECORD_EVENTS_TO") +} diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index 4172f3e49..853dae0d9 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -1,72 +1,29 @@ package integration import ( - "encoding/json" "errors" "fmt" "io/ioutil" - "log" "os" "os/exec" "path/filepath" - "strconv" - "strings" "testing" - "github.com/jesseduffield/generics/slices" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/secureexec" + "github.com/jesseduffield/lazygit/pkg/integration/integration_tests" + "github.com/jesseduffield/lazygit/pkg/integration/types" ) -// This package is for running our integration test suite. See docs/Integration_Tests.md for more info +// this is the integration runner for the new and improved integration interface -type Test struct { - Name string `json:"name"` - Speed float64 `json:"speed"` - Description string `json:"description"` - ExtraCmdArgs string `json:"extraCmdArgs"` - Skip bool `json:"skip"` -} - -type Mode int - -const ( - // default: for when we're just running a test and comparing to the snapshot - TEST = iota - // for when we want to record a test and set the snapshot based on the result - RECORD - // when we just want to use the setup of the test for our own sandboxing purposes. - // This does not record the session and does not create/update snapshots - SANDBOX - // running a test but updating the snapshot - UPDATE_SNAPSHOT -) - -func GetModeFromEnv() Mode { - switch os.Getenv("MODE") { - case "record": - return RECORD - case "", "test": - return TEST - case "updateSnapshot": - return UPDATE_SNAPSHOT - case "sandbox": - return SANDBOX - default: - log.Fatalf("unknown test mode: %s, must be one of [test, record, update, sandbox]", os.Getenv("MODE")) - panic("unreachable") - } -} +// re-exporting this so that clients only need to import one package +var Tests = integration_tests.Tests -// this function is used by both `go test` and from our lazyintegration gui, but -// errors need to be handled differently in each (for example go test is always -// working with *testing.T) so we pass in any differences as args here. -func RunTests( +func RunTestsNew( logf func(format string, formatArgs ...interface{}), runCmd func(cmd *exec.Cmd) error, - fnWrapper func(test *Test, f func(*testing.T) error), + fnWrapper func(test types.Test, f func(*testing.T) error), mode Mode, - speedEnv string, onFail func(t *testing.T, expected string, actual string, prefix string), includeSkipped bool, ) error { @@ -76,7 +33,7 @@ func RunTests( return err } - testDir := filepath.Join(rootDir, "test", "integration") + testDir := filepath.Join(rootDir, "test", "integration_new") osCommand := oscommands.NewDummyOSCommand() err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run() @@ -84,115 +41,94 @@ func RunTests( return err } - tests, err := LoadTests(testDir) - if err != nil { - return err - } - - for _, test := range tests { + for _, test := range Tests { test := test fnWrapper(test, func(t *testing.T) error { //nolint: thelper - if test.Skip && !includeSkipped { - logf("skipping test: %s", test.Name) + if test.Skip() && !includeSkipped { + logf("skipping test: %s", test.Name()) return nil } - speeds := getTestSpeeds(test.Speed, mode, speedEnv) - testPath := filepath.Join(testDir, test.Name) + testPath := filepath.Join(testDir, test.Name()) + actualDir := filepath.Join(testPath, "actual") expectedDir := filepath.Join(testPath, "expected") actualRepoDir := filepath.Join(actualDir, "repo") logf("path: %s", testPath) - for i, speed := range speeds { - if mode != SANDBOX && mode != RECORD { - logf("%s: attempting test at speed %f\n", test.Name, speed) - } + findOrCreateDir(testPath) + prepareIntegrationTestDir(actualDir) + findOrCreateDir(actualRepoDir) + err := createFixtureNew(test, actualRepoDir, rootDir) + if err != nil { + return err + } + + configDir := filepath.Join(testPath, "used_config") - findOrCreateDir(testPath) - prepareIntegrationTestDir(actualDir) - findOrCreateDir(actualRepoDir) - err := createFixture(testPath, actualRepoDir) + cmd, err := getLazygitCommandNew(test, testPath, rootDir) + if err != nil { + return err + } + + err = runCmd(cmd) + if err != nil { + return err + } + + if mode == UPDATE_SNAPSHOT { + // create/update snapshot + err = oscommands.CopyDir(actualDir, expectedDir) if err != nil { return err } - configDir := filepath.Join(testPath, "used_config") + if err := renameSpecialPaths(expectedDir); err != nil { + return err + } - cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs) - if err != nil { + logf("%s", "updated snapshot") + } else { + if err := validateSameRepos(expectedDir, actualDir); err != nil { return err } - err = runCmd(cmd) + // iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir + expectedFiles, err := ioutil.ReadDir(expectedDir) if err != nil { return err } - if mode == UPDATE_SNAPSHOT || mode == RECORD { - // create/update snapshot - err = oscommands.CopyDir(actualDir, expectedDir) - if err != nil { - return err + for _, f := range expectedFiles { + if !f.IsDir() { + return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory") } - if err := renameSpecialPaths(expectedDir); err != nil { - return err - } + // get corresponding file name from actual dir + actualRepoPath := filepath.Join(actualDir, f.Name()) + expectedRepoPath := filepath.Join(expectedDir, f.Name()) - logf("%s", "updated snapshot") - } else { - if err := validateSameRepos(expectedDir, actualDir); err != nil { - return err - } - - // iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir - expectedFiles, err := ioutil.ReadDir(expectedDir) + actualRepo, expectedRepo, err := generateSnapshots(actualRepoPath, expectedRepoPath) if err != nil { return err } - success := true - for _, f := range expectedFiles { - if !f.IsDir() { - return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory") - } - - // get corresponding file name from actual dir - actualRepoPath := filepath.Join(actualDir, f.Name()) - expectedRepoPath := filepath.Join(expectedDir, f.Name()) - - actualRepo, expectedRepo, err := generateSnapshots(actualRepoPath, expectedRepoPath) + if expectedRepo != actualRepo { + // get the log file and print it + bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) if err != nil { return err } + logf("%s", string(bytes)) - if expectedRepo != actualRepo { - success = false - // if the snapshot doesn't match and we haven't tried all playback speeds different we'll retry at a slower speed - if i < len(speeds)-1 { - break - } - - // get the log file and print it - bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) - if err != nil { - return err - } - logf("%s", string(bytes)) - - onFail(t, expectedRepo, actualRepo, f.Name()) - } - } - - if success { - logf("%s: success at speed %f\n", test.Name, speed) - break + onFail(t, expectedRepo, actualRepo, f.Name()) } } } + logf("test passed: %s", test.Name()) + return nil }) } @@ -200,344 +136,35 @@ func RunTests( return nil } -// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos) -func validateSameRepos(expectedDir string, actualDir string) error { - // iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir - expectedFiles, err := ioutil.ReadDir(expectedDir) - if err != nil { - return err - } - - var actualFiles []os.FileInfo - actualFiles, err = ioutil.ReadDir(actualDir) - if err != nil { - return err - } - - expectedFileNames := slices.Map(expectedFiles, getFileName) - actualFileNames := slices.Map(actualFiles, getFileName) - if !slices.Equal(expectedFileNames, actualFileNames) { - return fmt.Errorf("expected and actual repo dirs do not match: expected: %s, actual: %s", expectedFileNames, actualFileNames) - } - - return nil -} - -func getFileName(f os.FileInfo) string { - return f.Name() -} - -func prepareIntegrationTestDir(actualDir string) { - // remove contents of integration test directory - dir, err := ioutil.ReadDir(actualDir) - if err != nil { - if os.IsNotExist(err) { - err = os.Mkdir(actualDir, 0o777) - if err != nil { - panic(err) - } - } else { - panic(err) - } - } - for _, d := range dir { - os.RemoveAll(filepath.Join(actualDir, d.Name())) - } -} - -func GetRootDirectory() string { - path, err := os.Getwd() - if err != nil { +func createFixtureNew(test types.Test, actualDir string, rootDir string) error { + if err := os.Chdir(actualDir); err != nil { panic(err) } - for { - _, err := os.Stat(filepath.Join(path, ".git")) + shell := &ShellImpl{} + shell.RunCommand("git init") + shell.RunCommand(`git config user.email "CI@example.com"`) + shell.RunCommand(`git config user.name "CI"`) - if err == nil { - return path - } + test.SetupRepo(shell) - if !os.IsNotExist(err) { - panic(err) - } - - path = filepath.Dir(path) - - if path == "/" { - log.Fatal("must run in lazygit folder or child folder") - } - } -} - -func createFixture(testPath, actualDir string) error { - bashScriptPath := filepath.Join(testPath, "setup.sh") - cmd := secureexec.Command("bash", bashScriptPath, actualDir) - - if output, err := cmd.CombinedOutput(); err != nil { - return errors.New(string(output)) - } - - return nil -} - -func tempLazygitPath() string { - return filepath.Join("/tmp", "lazygit", "test_lazygit") -} - -func getTestSpeeds(testStartSpeed float64, mode Mode, speedStr string) []float64 { - if mode != TEST { - // have to go at original speed if updating snapshots in case we go to fast and create a junk snapshot - return []float64{1.0} - } - - if speedStr != "" { - speed, err := strconv.ParseFloat(speedStr, 64) - if err != nil { - panic(err) - } - return []float64{speed} - } - - // default is 10, 5, 1 - startSpeed := 10.0 - if testStartSpeed != 0 { - startSpeed = testStartSpeed - } - speeds := []float64{startSpeed} - if startSpeed > 5 { - speeds = append(speeds, 5) - } - speeds = append(speeds, 1, 1) - - return speeds -} - -func LoadTests(testDir string) ([]*Test, error) { - paths, err := filepath.Glob(filepath.Join(testDir, "/*/test.json")) - if err != nil { - return nil, err - } - - tests := make([]*Test, len(paths)) - - for i, path := range paths { - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - test := &Test{} - - err = json.Unmarshal(data, test) - if err != nil { - return nil, err - } - - test.Name = strings.TrimPrefix(filepath.Dir(path), testDir+"/") - - tests[i] = test - } - - return tests, nil -} - -func findOrCreateDir(path string) { - _, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - err = os.MkdirAll(path, 0o777) - if err != nil { - panic(err) - } - } else { - panic(err) - } - } -} - -// note that we don't actually store this snapshot in the lazygit repo. -// Instead we store the whole expected git repo of our test, so that -// we can easily change what we want to compare without needing to regenerate -// snapshots for each test. -func generateSnapshot(dir string) (string, error) { - osCommand := oscommands.NewDummyOSCommand() - - _, err := os.Stat(filepath.Join(dir, ".git")) - if err != nil { - return "git directory not found", nil - } - - snapshot := "" - - cmdStrs := []string{ - `remote show -n origin`, // remote branches - // TODO: find a way to bring this back without breaking tests - // `ls-remote origin`, - `status`, // file tree - `log --pretty=%B|%an|%ae -p -1`, // log - `tag -n`, // tags - `stash list`, // stash - `submodule foreach 'git status'`, // submodule status - `submodule foreach 'git log --pretty=%B -p -1'`, // submodule log - `submodule foreach 'git tag -n'`, // submodule tags - `submodule foreach 'git stash list'`, // submodule stash - } - - for _, cmdStr := range cmdStrs { - // ignoring error for now. If there's an error it could be that there are no results - output, _ := osCommand.Cmd.New(fmt.Sprintf("git -C %s %s", dir, cmdStr)).RunWithOutput() - - snapshot += fmt.Sprintf("git %s:\n%s\n", cmdStr, output) - } - - snapshot += "files in repo:\n" - err = filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - if f.IsDir() { - if f.Name() == ".git" { - return filepath.SkipDir - } - return nil - } - - bytes, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - relativePath, err := filepath.Rel(dir, path) - if err != nil { - return err - } - snapshot += fmt.Sprintf("path: %s\ncontent:\n%s\n", relativePath, string(bytes)) - - return nil - }) - - if err != nil { - return "", err - } - - return snapshot, nil -} - -func generateSnapshots(actualDir string, expectedDir string) (string, string, error) { - actual, err := generateSnapshot(actualDir) - if err != nil { - return "", "", err - } - - // there are a couple of reasons we're not generating the snapshot in expectedDir directly: - // Firstly we don't want to have to revert our .git file back to .git_keep. - // Secondly, the act of calling git commands like 'git status' actually changes the index - // for some reason, and we don't want to leave your lazygit working tree dirty as a result. - expectedDirCopyDir := filepath.Join(filepath.Dir(expectedDir), "expected_dir_test") - err = oscommands.CopyDir(expectedDir, expectedDirCopyDir) - if err != nil { - return "", "", err - } - - defer func() { - err := os.RemoveAll(expectedDirCopyDir) - if err != nil { - panic(err) - } - }() - - if err := restoreSpecialPaths(expectedDirCopyDir); err != nil { - return "", "", err - } - - expected, err := generateSnapshot(expectedDirCopyDir) - if err != nil { - return "", "", err - } - - return actual, expected, nil -} - -func getPathsToRename(dir string, needle string, contains string) []string { - pathsToRename := []string{} - - err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - if f.Name() == needle && (contains == "" || strings.Contains(path, contains)) { - pathsToRename = append(pathsToRename, path) - } - - return nil - }) - if err != nil { + // changing directory back to rootDir after the setup is done + if err := os.Chdir(rootDir); err != nil { panic(err) } - return pathsToRename -} - -var specialPathMappings = []struct{ original, new, contains string }{ - // git refuses to track .git or .gitmodules in subdirectories so we need to rename them - {".git", ".git_keep", ""}, - {".gitmodules", ".gitmodules_keep", ""}, - // we also need git to ignore the contents of our test gitignore files so that - // we actually commit files that are ignored within the test. - {".gitignore", "lg_ignore_file", ""}, - // this is the .git/info/exclude file. We're being a little more specific here - // so that we don't accidentally mess with some other file named 'exclude' in the test. - {"exclude", "lg_exclude_file", ".git/info/exclude"}, -} - -func renameSpecialPaths(dir string) error { - for _, specialPath := range specialPathMappings { - for _, path := range getPathsToRename(dir, specialPath.original, specialPath.contains) { - err := os.Rename(path, filepath.Join(filepath.Dir(path), specialPath.new)) - if err != nil { - return err - } - } - } - return nil } -func restoreSpecialPaths(dir string) error { - for _, specialPath := range specialPathMappings { - for _, path := range getPathsToRename(dir, specialPath.new, specialPath.contains) { - err := os.Rename(path, filepath.Join(filepath.Dir(path), specialPath.original)) - if err != nil { - return err - } - } - } - - return nil -} - -func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64, extraCmdArgs string) (*exec.Cmd, error) { +func getLazygitCommandNew(test types.Test, testPath string, rootDir string) (*exec.Cmd, error) { osCommand := oscommands.NewDummyOSCommand() - replayPath := filepath.Join(testPath, "recording.json") templateConfigDir := filepath.Join(rootDir, "test", "default_test_config") actualRepoDir := filepath.Join(testPath, "actual", "repo") - exists, err := osCommand.FileExists(filepath.Join(testPath, "config")) - if err != nil { - return nil, err - } - - if exists { - templateConfigDir = filepath.Join(testPath, "config") - } - configDir := filepath.Join(testPath, "used_config") - err = os.RemoveAll(configDir) + err := os.RemoveAll(configDir) if err != nil { return nil, err } @@ -546,17 +173,11 @@ func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64 return nil, err } - cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualRepoDir, extraCmdArgs) + cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualRepoDir, test.ExtraCmdArgs()) cmdObj := osCommand.Cmd.New(cmdStr) - cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed)) - switch mode { - case RECORD: - cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath)) - case TEST, UPDATE_SNAPSHOT: - cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath)) - } + cmdObj.AddEnvVars(fmt.Sprintf("LAZYGIT_TEST_NAME=%s", test.Name())) return cmdObj.GetCmd(), nil } diff --git a/pkg/integration/integration_old.go b/pkg/integration/integration_old.go new file mode 100644 index 000000000..579892e3b --- /dev/null +++ b/pkg/integration/integration_old.go @@ -0,0 +1,564 @@ +package integration + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/secureexec" +) + +// This package is for running our integration test suite. See docs/Integration_Tests.md for more info. + +// Deprecated: This file is part of the old way of doing things. See integration.go for the new way + +type Test struct { + Name string `json:"name"` + Speed float64 `json:"speed"` + Description string `json:"description"` + ExtraCmdArgs string `json:"extraCmdArgs"` + Skip bool `json:"skip"` +} + +type Mode int + +const ( + // default: for when we're just running a test and comparing to the snapshot + TEST = iota + // for when we want to record a test and set the snapshot based on the result + RECORD + // when we just want to use the setup of the test for our own sandboxing purposes. + // This does not record the session and does not create/update snapshots + SANDBOX + // running a test but updating the snapshot + UPDATE_SNAPSHOT +) + +func GetModeFromEnv() Mode { + switch os.Getenv("MODE") { + case "record": + return RECORD + case "", "test": + return TEST + case "updateSnapshot": + return UPDATE_SNAPSHOT + case "sandbox": + return SANDBOX + default: + log.Fatalf("unknown test mode: %s, must be one of [test, record, updateSnapshot, sandbox]", os.Getenv("MODE")) + panic("unreachable") + } +} + +// this function is used by both `go test` and from our lazyintegration gui, but +// errors need to be handled differently in each (for example go test is always +// working with *testing.T) so we pass in any differences as args here. +func RunTests( + logf func(format string, formatArgs ...interface{}), + runCmd func(cmd *exec.Cmd) error, + fnWrapper func(test *Test, f func(*testing.T) error),