Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

naxhh
Copy link
Contributor

@naxhh naxhh commented Apr 6, 2020

Fixes #538
Related issue: #536

Provide tests for gitutil as discussed in #538
Because of the nature of EnsureCloned and EnsureUpdated 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 a git init && git remote add origin {url} maybe is better to expose a function that does that instead of exposing Exec 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 as EnsureClone + the update part. Should we keep both exposed?

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Apr 6, 2020
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: naxhh
To complete the pull request process, please assign corneliusweig
You can assign the PR to them by writing /assign @corneliusweig in a comment when ready.

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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Apr 6, 2020
@ahmetb
Copy link
Member

ahmetb commented Apr 6, 2020

Regarding Exec I see it only used in a test to do a git init && git remote add origin {url} maybe is better to expose a function that does that instead of exposing Exec i didn't perform tests on it since is used everywhere in the rest of the tested code.

Yeah don't worry about this.

func assertRepoIsCloned(t *testing.T, repoPath string) {
t.Helper()

ok, err := IsGitCloned(repoPath)
Copy link
Member

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/.

Copy link
Contributor Author

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

Copy link
Contributor

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.

Comment on lines +43 to +44
httpClonePath := tempDir.Path("krew-from-https")
localClonePath := tempDir.Path("krew-from-local-path")
Copy link
Member

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?

Copy link
Contributor Author

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"))

Copy link
Member

@ahmetb ahmetb Apr 6, 2020

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 :)

Comment on lines +79 to +126
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")
}
}
Copy link
Member

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

Copy link
Contributor Author

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 {
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Contributor

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.

@ahmetb
Copy link
Member

ahmetb commented Apr 6, 2020

thanks for this work, it is SUPER appreciated.
coincidentally today I took a pass on removing git dependency from gitutil (excl. tests) using go-git.v5 and the results look promising. 👍

@ahmetb
Copy link
Member

ahmetb commented Apr 7, 2020

Finally I noticed EnsureUpdate does the same as EnsureClone + the update part. Should we keep both exposed?

I think it depends on the usage outside the pkg. If it's not used outside the pkg, you can unexport.

@naxhh
Copy link
Contributor Author

naxhh commented Apr 7, 2020

Finally I noticed EnsureUpdate does the same as EnsureClone + the update part. Should we keep both exposed?

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.

@naxhh
Copy link
Contributor Author

naxhh commented Apr 7, 2020

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

level=warning msg="[runner] Can't run linter goanalysis_metalinter: deadcode: analysis skipped: errors in package: [/home/runner/work/krew/krew/cmd/krew/main.go:20:2: could not import sigs.k8s.io/krew/cmd/krew/cmd (/home/runner/work/krew/krew/cmd/krew/cmd/search.go:50:21: IndexURI not declared by package constants)]"

In local i didn't see this error at any point

@ahmetb
Copy link
Member

ahmetb commented Apr 7, 2020

We changed the const to DefaultIndexUri. Not sure why the compilation hasn’t failed instead.

@ahmetb
Copy link
Member

ahmetb commented Apr 7, 2020

@naxhh sorry it appears that a previous patch broke the code. I sent #590 to fix it. I still do not know why that PRs tests have not failed, this one’s currently mysterious.

@ahmetb
Copy link
Member

ahmetb commented Apr 7, 2020

Please rebase on top of master now.

Copy link
Contributor

@corneliusweig corneliusweig left a 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2019 The Kubernetes Authors.
// Copyright 2020 The Kubernetes Authors.

😉

Comment on lines +113 to +115
if err := os.MkdirAll(tempDir.Path(".git"), os.ModePerm); err != nil {
t.Fatalf("cannot create directory %q: %s", filepath.Dir(tempDir.Path(".git")), err)
}
Copy link
Contributor

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)
Copy link
Contributor

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.

Comment on lines +46 to +52
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)
}
Copy link
Contributor

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 {
Copy link
Contributor

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:

  1. Create a util similar to
    func (it *ITest) initializeIndex() {
    (or extract it to a common place).
  2. 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?

Copy link
Contributor Author

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"))
Copy link
Contributor

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 {
Copy link
Contributor

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 {
Copy link
Contributor

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.

Comment on lines +147 to +149
lastCommitID, err := Exec(tempDir.Root(), "rev-parse", "HEAD")

if err != nil {
Copy link
Contributor

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)

@ahmetb
Copy link
Member

ahmetb commented Apr 29, 2020

@naxhh Gently pinging, when you have time PTAL at the comments.

@ahmetb
Copy link
Member

ahmetb commented May 6, 2020

@naxhh Ping again.

@naxhh
Copy link
Contributor Author

naxhh commented May 6, 2020

Sorry I've been busy changing jobs. I'll try to make some time and finish this

@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Aug 4, 2020
@corneliusweig
Copy link
Contributor

@naxhh Polite ping. Would be really great to have these tests.

@fejta-bot
Copy link

Stale issues rot after 30d of inactivity.
Mark the issue as fresh with /remove-lifecycle rotten.
Rotten issues close after an additional 30d of inactivity.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle rotten

@k8s-ci-robot k8s-ci-robot added lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Sep 14, 2020
@fejta-bot
Copy link

Rotten issues close after 30d of inactivity.
Reopen the issue with /reopen.
Mark the issue as fresh with /remove-lifecycle rotten.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/close

@k8s-ci-robot
Copy link
Contributor

@fejta-bot: Closed this PR.

In response to this:

Rotten issues close after 30d of inactivity.
Reopen the issue with /reopen.
Mark the issue as fresh with /remove-lifecycle rotten.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/close

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add unit tests for internal/gitutil pkg
5 participants