New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add tests for gitutil #587
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: naxhh The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Yeah don't worry about this. |
func assertRepoIsCloned(t *testing.T, repoPath string) { | ||
t.Helper() | ||
|
||
ok, err := IsGitCloned(repoPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally test code shouldn't depend on functionality that's being tested.
in this case, IsGitCloned should have a test.
so please consider actually using ioutil.ReadDir and verify the repoPath has >2 files, one of them being .git/
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. that sounds ok to me. I was doing it this way because i've specific tests to check if IsGitCloned
worked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can also shell out to git
to check if it is a repo. Then we don't even need to assume anything about the internals of git repositories.
Other than that, I think your approach has drawbacks as Ahmet pointed out, but it should be fine. After all, we can assume that IsGitCloned
is pretty solid.
httpClonePath := tempDir.Path("krew-from-https") | ||
localClonePath := tempDir.Path("krew-from-local-path") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you mind splitting into two tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I kept them together to don't make the process slower. but i'll split them
} | ||
|
||
_, err := os.Stat(tempDir.Path("file")) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you mind removing unnecessary empty lines, there are far too many :)
func TestIsGitClonedWhenPathDoesntExists(t *testing.T) { | ||
tempDir, cleanup := testutil.NewTempDir(t) | ||
defer cleanup() | ||
|
||
ok, err := IsGitCloned(tempDir.Path("does-not-exist")) | ||
|
||
if err != nil { | ||
t.Errorf("expected to finish correctly: %s", err) | ||
} | ||
|
||
if ok != false { | ||
t.Errorf("expected to return false on a non existing folder") | ||
} | ||
} | ||
|
||
func TestIsGitClonedWhenIsFalse(t *testing.T) { | ||
tempDir, cleanup := testutil.NewTempDir(t) | ||
defer cleanup() | ||
|
||
ok, err := IsGitCloned(tempDir.Root()) | ||
|
||
if err != nil { | ||
t.Errorf("expected to finish correctly: %s", err) | ||
} | ||
|
||
if ok != false { | ||
t.Errorf("expected to return false on a folder that's not a git repo") | ||
} | ||
} | ||
|
||
func TestIsGitClonedWhenIsTrue(t *testing.T) { | ||
tempDir, cleanup := testutil.NewTempDir(t) | ||
defer cleanup() | ||
|
||
if err := os.MkdirAll(tempDir.Path(".git"), os.ModePerm); err != nil { | ||
t.Fatalf("cannot create directory %q: %s", filepath.Dir(tempDir.Path(".git")), err) | ||
} | ||
|
||
ok, err := IsGitCloned(tempDir.Root()) | ||
|
||
if err != nil { | ||
t.Errorf("expected to finish correctly: %s", err) | ||
} | ||
|
||
if ok != true { | ||
t.Errorf("expected to return true on a git repo") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can actually merge all these into 1 test case very easily. and merge if-if
to if-elseif
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the two first tests, sure I'll merge.
The last one has a custom mkdirall
as part of the setup so I think merging them will make the code less clear? how would you merge this one with the others normally?
tempDir, cleanup := testutil.NewTempDir(t) | ||
defer cleanup() | ||
|
||
if err := EnsureUpdated("https://github.com/kubernetes-sigs/krew.git", tempDir.Root()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this test is using the network, it's quite bad for a unit test (which is supposed to run fast).
if possible, we should be using a local directory as the remote repo. for that, we need to init a dir, maybe add a file and make 1 or 2 commits, then clone that dir.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I was asking if I should mock the Exec
call. to make them faster and don't depend on git and other stuff.
I can make all tests use a local git repo and only the one about https working to use the url if that sounds ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mocking Exec
should not be necessary. I think it's fine to clone a local repository (see other comment above).
Also, I don't think that cloning from a remote repo is something that we should test in unit tests.
thanks for this work, it is SUPER appreciated. |
I think it depends on the usage outside the pkg. If it's not used outside the pkg, you can unexport. |
It's being used so I'll keep them. Today or tomorrow I'll upload the fixes. thanks for your time. |
Btw the ci failed on the linting part. but the error doesn't give me a hint if the problem is mine or is unrelated
In local i didn't see this error at any point |
We changed the const to DefaultIndexUri. Not sure why the compilation hasn’t failed instead. |
Please rebase on top of master now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @naxhh, thanks for taking this on. This is much needed and not completely straightforward to set up. I very much like what you test. But I think we need to revisit how it's best done. I left a few comments inline, but one overarching theme is that these tests rely a lot on cloning from a remote repository.
This is not ideal, because as unit tests, they are expected to run very fast, and not have external dependencies. To overcome this, I suggest you add a test tool to set up a git repository and add a util to add trivial commits. This will be much faster than cloning from remote, and also is more self-contained.
@@ -0,0 +1,213 @@ | |||
// Copyright 2019 The Kubernetes Authors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Copyright 2019 The Kubernetes Authors. | |
// Copyright 2020 The Kubernetes Authors. |
😉
if err := os.MkdirAll(tempDir.Path(".git"), os.ModePerm); err != nil { | ||
t.Fatalf("cannot create directory %q: %s", filepath.Dir(tempDir.Path(".git")), err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this is nit-picking. But you are setting the test up in a way that tests your (and git's) implementation. Could you come up with a way where you actually create a git repo in that directory? That way, you would test the behaviour of IsGitCloned
in a more reliable way.
func assertRepoIsCloned(t *testing.T, repoPath string) { | ||
t.Helper() | ||
|
||
ok, err := IsGitCloned(repoPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can also shell out to git
to check if it is a repo. Then we don't even need to assume anything about the internals of git repositories.
Other than that, I think your approach has drawbacks as Ahmet pointed out, but it should be fine. After all, we can assume that IsGitCloned
is pretty solid.
if err := EnsureCloned("https://github.com/kubernetes-sigs/krew.git", httpClonePath); err != nil { | ||
t.Errorf("http clone expected to finish correctly: %s", err) | ||
} | ||
|
||
if err := EnsureCloned(httpClonePath, localClonePath); err != nil { | ||
t.Errorf("folder clone expected to finish correctly: %s", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO one of the two cases is enough. Otherwise we are testing if git accepts HTTP URLs or local file paths.
httpClonePath := tempDir.Path("krew-from-https") | ||
localClonePath := tempDir.Path("krew-from-local-path") | ||
|
||
if err := EnsureCloned("https://github.com/kubernetes-sigs/krew.git", httpClonePath); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cloning krew is not a good idea IMO. It is far too large to be cloned for an ordinary unit test.
We should really strive to not have to clone from remote too often. There are basically two ways to approach this:
- Create a util similar to
krew/integration_test/testutil_test.go
Line 265 in b2b8a40
func (it *ITest) initializeIndex() { - Set up a repo by shelling out to
git
and clone that repo like localClonePath.
It looks to me like 2) is more straightforward.
(same in other test cases)
|
||
tempDir.Write("file", []byte("create a file to ensure is not overwriten in the next clone")) | ||
|
||
if err := EnsureCloned("https://github.com/kubernetes-sigs/krew.git", tempDir.Root()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if err := EnsureCloned("https://github.com/kubernetes-sigs/krew.git", tempDir.Root()); err != nil { | |
if err := EnsureCloned("https://github.com/kubernetes-sigs/krew.git", tempDir.Root()); err == nil { |
I would expect that the second invocation provokes an error. Are you sure this is correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll double check but I think it just returned without an error.
t.Errorf("clone expected to finish correctly: %s", err) | ||
} | ||
|
||
tempDir.Write("file", []byte("create a file to ensure is not overwriten in the next clone")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not needed to provoke an error in git. Consider removing it.
tempDir, cleanup := testutil.NewTempDir(t) | ||
defer cleanup() | ||
|
||
if err := EnsureCloned("https://github.com/kubernetes-sigs/krew.git", tempDir.Root()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you continue to use the same repo in all tests, please extract the URL to a package-local constant.
tempDir, cleanup := testutil.NewTempDir(t) | ||
defer cleanup() | ||
|
||
if err := EnsureUpdated("https://github.com/kubernetes-sigs/krew.git", tempDir.Root()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mocking Exec
should not be necessary. I think it's fine to clone a local repository (see other comment above).
Also, I don't think that cloning from a remote repo is something that we should test in unit tests.
lastCommitID, err := Exec(tempDir.Root(), "rev-parse", "HEAD") | ||
|
||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the blank line. The error check really belongs to the previous line and should be grouped with it. (Same in other places)
@naxhh Gently pinging, when you have time PTAL at the comments. |
@naxhh Ping again. |
Sorry I've been busy changing jobs. I'll try to make some time and finish this |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
@naxhh Polite ping. Would be really great to have these tests. |
Stale issues rot after 30d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
Rotten issues close after 30d of inactivity. Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
@fejta-bot: Closed this PR. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Fixes #538
Related issue: #536
Provide tests for gitutil as discussed in #538
Because of the nature of
EnsureCloned
andEnsureUpdated
this tests are quite slow compared to the rest of the suite (around 25 seconds) maybe would be good to mark most of them to be avoided in short runs, or mock the Exec calls to avoid doing fetches.Regarding
Exec
I see it only used in a test to do agit init && git remote add origin {url}
maybe is better to expose a function that does that instead of exposingExec
i didn't perform tests on it since is used everywhere in the rest of the tested code.Finally I noticed
EnsureUpdate
does the same asEnsureClone
+ the update part. Should we keep both exposed?