context_test.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dep

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "runtime"
    "strings"
    "testing"
    "unicode"

    "github.com/golang/dep/internal/test"
)

func discardLogger() *log.Logger {
    return log.New(ioutil.Discard, "", 0)
}

func TestCtx_ProjectImport(t *testing.T) {
    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir("src")

    h.Setenv("GOPATH", h.Path("."))
    depCtx := &Ctx{GOPATH: h.Path(".")}

    importPaths := []string{
        "github.com/pkg/errors",
        "my/silly/thing",
    }

    for _, want := range importPaths {
        fullpath := filepath.Join(depCtx.GOPATH, "src", want)
        h.TempDir(filepath.Join("src", want))
        got, err := depCtx.ImportForAbs(fullpath)
        if err != nil {
            t.Fatal(err)
        }
        if got != want {
            t.Fatalf("expected %s, got %s", want, got)
        }
    }

    // test where it should return an error when directly within $GOPATH/src
    got, err := depCtx.ImportForAbs(filepath.Join(depCtx.GOPATH, "src"))
    if err == nil || !strings.Contains(err.Error(), "GOPATH/src") {
        t.Fatalf("should have gotten an error for use directly in GOPATH/src, but got %s", got)
    }

    // test where it should return an error
    got, err = depCtx.ImportForAbs("tra/la/la/la")
    if err == nil {
        t.Fatalf("should have gotten an error but did not for tra/la/la/la: %s", got)
    }
}

func TestAbsoluteProjectRoot(t *testing.T) {
    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir("src")
    h.Setenv("GOPATH", h.Path("."))
    depCtx := &Ctx{GOPATH: h.Path(".")}

    importPaths := map[string]bool{
        "github.com/pkg/errors": true,
        "my/silly/thing":        false,
    }

    for i, create := range importPaths {
        if create {
            h.TempDir(filepath.Join("src", i))
        }
    }

    for i, ok := range importPaths {
        got, err := depCtx.AbsForImport(i)
        if ok {
            h.Must(err)
            want := h.Path(filepath.Join("src", i))
            if got != want {
                t.Fatalf("expected %s, got %q", want, got)
            }
            continue
        }

        if err == nil {
            t.Fatalf("expected %s to fail", i)
        }
    }

    // test that a file fails
    h.TempFile("src/thing/thing.go", "hello world")
    _, err := depCtx.AbsForImport("thing/thing.go")
    if err == nil {
        t.Fatal("error should not be nil for a file found")
    }
}

func TestLoadProject(t *testing.T) {
    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir(filepath.Join("src", "test1", "sub"))
    h.TempFile(filepath.Join("src", "test1", ManifestName), "")
    h.TempFile(filepath.Join("src", "test1", LockName), `memo = "cdafe8641b28cd16fe025df278b0a49b9416859345d8b6ba0ace0272b74925ee"`)
    h.TempDir(filepath.Join("src", "test2", "sub"))
    h.TempFile(filepath.Join("src", "test2", ManifestName), "")

    var testcases = []struct {
        name string
        lock bool
        wd   string
    }{
        {"direct", true, filepath.Join("src", "test1")},
        {"ascending", true, filepath.Join("src", "test1", "sub")},
        {"without lock", false, filepath.Join("src", "test2")},
        {"ascending without lock", false, filepath.Join("src", "test2", "sub")},
    }

    for _, tc := range testcases {
        t.Run(tc.name, func(t *testing.T) {
            ctx := &Ctx{
                Out: discardLogger(),
                Err: discardLogger(),
            }

            err := ctx.SetPaths(h.Path(tc.wd), h.Path("."))
            if err != nil {
                t.Fatalf("%+v", err)
            }

            p, err := ctx.LoadProject()
            switch {
            case err != nil:
                t.Fatalf("%s: LoadProject failed: %+v", tc.wd, err)
            case p.Manifest == nil:
                t.Fatalf("%s: Manifest file didn't load", tc.wd)
            case tc.lock && p.Lock == nil:
                t.Fatalf("%s: Lock file didn't load", tc.wd)
            case !tc.lock && p.Lock != nil:
                t.Fatalf("%s: Non-existent Lock file loaded", tc.wd)
            }
        })
    }
}

func TestExplicitRootProject(t *testing.T) {
    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir(filepath.Join("src", "test1", "sub"))
    h.TempFile(filepath.Join("src", "test1", ManifestName), "")
    h.TempFile(filepath.Join("src", "test1", LockName), `memo = "cdafe8641b28cd16fe025df278b0a49b9416859345d8b6ba0ace0272b74925ee"`)
    h.TempDir(filepath.Join("src", "test2", "sub"))
    h.TempFile(filepath.Join("src", "test2", ManifestName), "")
    h.Setenv("DEP_PROJECT_ROOT", "github.com/user/module")

    type tcase struct {
        name string
        lock bool
        wd   string
    }
    var testcases = []tcase{
        {"direct", true, filepath.Join("src", "test1")},
        {"ascending", true, filepath.Join("src", "test1", "sub")},
        {"without lock", false, filepath.Join("src", "test2")},
        {"ascending without lock", false, filepath.Join("src", "test2", "sub")},
    }

    tf := func(withGOPATH bool, tc tcase, t *testing.T) func(t *testing.T) {
        return func(t *testing.T) {
            ctx := &Ctx{
                Out: discardLogger(),
                Err: discardLogger(),
            }

            var err error
            if withGOPATH {
                err = ctx.SetPaths(h.Path(tc.wd), h.Path("."))
            } else {
                err = ctx.SetPaths(h.Path(tc.wd))
            }
            if err != nil {
                t.Fatalf("%+v", err)
            }
            ctx.ExplicitRoot = "github.com/user/module"

            p, err := ctx.LoadProject()
            switch {
            case err != nil:
                t.Fatalf("%s: LoadProject failed: %+v", tc.wd, err)
            case p.Manifest == nil:
                t.Fatalf("%s: Manifest file didn't load", tc.wd)
            case tc.lock && p.Lock == nil:
                t.Fatalf("%s: Lock file didn't load", tc.wd)
            case !tc.lock && p.Lock != nil:
                t.Fatalf("%s: Non-existent Lock file loaded", tc.wd)
            }
        }
    }

    for _, tc := range testcases {
        t.Run(tc.name, func(t *testing.T) {
            t.Run("within-GOPATH", tf(true, tc, t))
            t.Run("outside-GOPATH", tf(false, tc, t))
        })
    }
}

func TestLoadProjectNotFoundErrors(t *testing.T) {
    tg := test.NewHelper(t)
    defer tg.Cleanup()

    tg.TempDir("src")
    tg.TempDir("src/test1")
    tg.TempDir("src/test1/sub")
    tg.Setenv("GOPATH", tg.Path("."))

    var testcases = []struct {
        lock  bool
        start string
        path  string
    }{
        {true, filepath.Join("src", "test1"), ""},        //direct
        {true, filepath.Join("src", "test1", "sub"), ""}, //ascending
    }

    for _, testcase := range testcases {
        ctx := &Ctx{GOPATHs: []string{tg.Path(".")}, WorkingDir: tg.Path(testcase.start)}

        _, err := ctx.LoadProject()
        if err == nil {
            t.Errorf("%s: should have returned 'No Manifest Found' error", testcase.start)
        }
    }
}

func TestLoadProjectManifestParseError(t *testing.T) {
    tg := test.NewHelper(t)
    defer tg.Cleanup()

    tg.TempDir("src")
    tg.TempDir("src/test1")
    tg.TempFile(filepath.Join("src/test1", ManifestName), `[[constraint]]`)
    tg.TempFile(filepath.Join("src/test1", LockName), `memo = "cdafe8641b28cd16fe025df278b0a49b9416859345d8b6ba0ace0272b74925ee"\n\n[[projects]]`)
    tg.Setenv("GOPATH", tg.Path("."))

    path := filepath.Join("src", "test1")
    tg.Cd(tg.Path(path))

    wd, err := os.Getwd()
    if err != nil {
        t.Fatal("failed to get working directory", err)
    }

    ctx := &Ctx{
        GOPATH:     tg.Path("."),
        WorkingDir: wd,
        Out:        discardLogger(),
        Err:        discardLogger(),
    }

    _, err = ctx.LoadProject()
    if err == nil {
        t.Fatal("should have returned 'Manifest Syntax' error")
    }
}

func TestLoadProjectLockParseError(t *testing.T) {
    tg := test.NewHelper(t)
    defer tg.Cleanup()

    tg.TempDir("src")
    tg.TempDir("src/test1")
    tg.TempFile(filepath.Join("src/test1", ManifestName), `[[constraint]]`)
    tg.TempFile(filepath.Join("src/test1", LockName), `memo = "cdafe8641b28cd16fe025df278b0a49b9416859345d8b6ba0ace0272b74925ee"\n\n[[projects]]`)
    tg.Setenv("GOPATH", tg.Path("."))

    path := filepath.Join("src", "test1")
    tg.Cd(tg.Path(path))

    wd, err := os.Getwd()
    if err != nil {
        t.Fatal("failed to get working directory", err)
    }

    ctx := &Ctx{
        GOPATH:     tg.Path("."),
        WorkingDir: wd,
        Out:        discardLogger(),
        Err:        discardLogger(),
    }

    _, err = ctx.LoadProject()
    if err == nil {
        t.Fatal("should have returned 'Lock Syntax' error")
    }
}

func TestLoadProjectNoSrcDir(t *testing.T) {
    tg := test.NewHelper(t)
    defer tg.Cleanup()

    tg.TempDir("test1")
    tg.TempFile(filepath.Join("test1", ManifestName), `[[constraint]]`)
    tg.TempFile(filepath.Join("test1", LockName), `memo = "cdafe8641b28cd16fe025df278b0a49b9416859345d8b6ba0ace0272b74925ee"\n\n[[projects]]`)
    tg.Setenv("GOPATH", tg.Path("."))

    ctx := &Ctx{GOPATH: tg.Path(".")}
    path := filepath.Join("test1")
    tg.Cd(tg.Path(path))

    f, _ := os.OpenFile(filepath.Join(ctx.GOPATH, "src", "test1", LockName), os.O_WRONLY, os.ModePerm)
    defer f.Close()

    _, err := ctx.LoadProject()
    if err == nil {
        t.Fatal("should have returned 'Split Absolute Root' error (no 'src' dir present)")
    }
}

func TestLoadProjectGopkgFilenames(t *testing.T) {
    // We are trying to skip this test on file systems which are case-sensiive. We could
    // have used `fs.IsCaseSensitiveFilesystem` for this check. However, the code we are
    // testing also relies on `fs.IsCaseSensitiveFilesystem`. So a bug in
    // `fs.IsCaseSensitiveFilesystem` could prevent this test from being run. This is the
    // only scenario where we prefer the OS heuristic over doing the actual work of
    // validating filesystem case sensitivity via `fs.IsCaseSensitiveFilesystem`.
    if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
        t.Skip("skip this test on non-Windows, non-macOS")
    }

    // Here we test that a manifest filename with incorrect case throws an error. Similar
    // error will also be thrown for the lock file as well which has been tested in
    // `project_test.go#TestCheckGopkgFilenames`. So not repeating here.

    h := test.NewHelper(t)
    defer h.Cleanup()

    invalidMfName := strings.ToLower(ManifestName)

    wd := filepath.Join("src", "test")
    h.TempFile(filepath.Join(wd, invalidMfName), "")

    ctx := &Ctx{
        Out: discardLogger(),
        Err: discardLogger(),
    }

    err := ctx.SetPaths(h.Path(wd), h.Path("."))
    if err != nil {
        t.Fatalf("%+v", err)
    }

    _, err = ctx.LoadProject()

    if err == nil {
        t.Fatal("should have returned 'Manifest Filename' error")
    }

    expectedErrMsg := fmt.Sprintf(
        "manifest filename %q does not match %q",
        invalidMfName, ManifestName,
    )

    if err.Error() != expectedErrMsg {
        t.Fatalf("unexpected error: %+v", err)
    }
}

// TestCaseInsensitive is test for Windows. This should work even though set
// difference letter cases in GOPATH.
func TestCaseInsensitiveGOPATH(t *testing.T) {
    if runtime.GOOS != "windows" {
        t.Skip("skip this test on non-Windows")
    }

    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir("src")
    h.TempDir("src/test1")
    h.TempFile(filepath.Join("src/test1", ManifestName), `
    [[constraint]]
        name = "github.com/foo/bar"
        branch = "master"`)

    // Shuffle letter case
    rs := []rune(strings.ToLower(h.Path(".")))
    for i, r := range rs {
        if unicode.IsLower(r) {
            rs[i] = unicode.ToUpper(r)
        } else {
            rs[i] = unicode.ToLower(r)
        }
    }
    gopath := string(rs)
    h.Setenv("GOPATH", gopath)
    wd := h.Path("src/test1")

    depCtx := &Ctx{}
    if err := depCtx.SetPaths(wd, gopath); err != nil {
        t.Fatal(err)
    }
    if _, err := depCtx.LoadProject(); err != nil {
        t.Fatal(err)
    }

    ip := "github.com/pkg/errors"
    fullpath := filepath.Join(depCtx.GOPATH, "src", ip)
    h.TempDir(filepath.Join("src", ip))
    pr, err := depCtx.ImportForAbs(fullpath)
    if err != nil {
        t.Fatal(err)
    }
    if pr != ip {
        t.Fatalf("expected %s, got %s", ip, pr)
    }
}

func TestDetectProjectGOPATH(t *testing.T) {
    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir(filepath.Join("sym", "symlink"))
    h.TempDir(filepath.Join("go", "src", "sym", "path"))
    h.TempDir(filepath.Join("go", "src", "real", "path"))
    h.TempDir(filepath.Join("go-two", "src", "real", "path"))
    h.TempDir(filepath.Join("go-two", "src", "sym"))

    ctx := &Ctx{
        GOPATHs: []string{h.Path("go"), h.Path("go-two")},
    }

    testcases := []struct {
        name         string
        root         string
        resolvedRoot string
        GOPATH       string
        expectErr    bool
    }{
        {
            name:         "project-with-no-AbsRoot",
            root:         "",
            resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
            expectErr:    true,
        },
        {
            name:         "project-with-no-ResolvedAbsRoot",
            root:         filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
            resolvedRoot: "",
            expectErr:    true,
        },
        {
            name:         "AbsRoot-is-not-within-any-GOPATH",
            root:         filepath.Join(h.Path("."), "src", "real", "path"),
            resolvedRoot: filepath.Join(h.Path("."), "src", "real", "path"),
            expectErr:    true,
        },
        {
            name:         "neither-AbsRoot-nor-ResolvedAbsRoot-are-in-any-GOPATH",
            root:         filepath.Join(h.Path("."), "src", "sym", "path"),
            resolvedRoot: filepath.Join(h.Path("."), "src", "real", "path"),
            expectErr:    true,
        },
        {
            name:         "both-AbsRoot-and-ResolvedAbsRoot-are-in-the-same-GOPATH",
            root:         filepath.Join(ctx.GOPATHs[0], "src", "sym", "path"),
            resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
            expectErr:    true,
        },
        {
            name:         "AbsRoot-and-ResolvedAbsRoot-are-each-within-a-different-GOPATH",
            root:         filepath.Join(ctx.GOPATHs[0], "src", "sym", "path"),
            resolvedRoot: filepath.Join(ctx.GOPATHs[1], "src", "real", "path"),
            expectErr:    true,
        },
        {
            name:         "AbsRoot-is-not-a-symlink",
            root:         filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
            resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
            GOPATH:       ctx.GOPATHs[0],
        },
        {
            name:         "AbsRoot-is-a-symlink-to-ResolvedAbsRoot",
            root:         filepath.Join(h.Path("."), "sym", "symlink"),
            resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
            GOPATH:       ctx.GOPATHs[0],
        },
    }

    for _, tc := range testcases {
        t.Run(tc.name, func(t *testing.T) {
            project := &Project{
                AbsRoot:         tc.root,
                ResolvedAbsRoot: tc.resolvedRoot,
            }

            GOPATH, err := ctx.DetectProjectGOPATH(project)
            if !tc.expectErr && err != nil {
                t.Fatalf("%+v", err)
            } else if tc.expectErr && err == nil {
                t.Fatalf("expected an error, got nil and gopath %s", GOPATH)
            }
            if GOPATH != tc.GOPATH {
                t.Errorf("expected GOPATH %s, got %s", tc.GOPATH, GOPATH)
            }
        })
    }
}

func TestDetectGOPATH(t *testing.T) {
    th := test.NewHelper(t)
    defer th.Cleanup()

    th.TempDir(filepath.Join("code", "src", "github.com", "username", "package"))
    th.TempDir(filepath.Join("go", "src", "github.com", "username", "package"))
    th.TempDir(filepath.Join("gotwo", "src", "github.com", "username", "package"))
    th.TempDir(filepath.Join("gothree", "sep", "src", "github.com", "username", "package"))

    sep := string(os.PathSeparator)

    ctx := &Ctx{GOPATHs: []string{
        th.Path("go"),
        th.Path("gotwo"),
        th.Path("gothree") + sep + sep + "sep",
    }}

    testcases := []struct {
        GOPATH string
        path   string
        err    bool
    }{
        {th.Path("go"), th.Path(filepath.Join("go", "src", "github.com", "username", "package")), false},
        {th.Path("go"), th.Path(filepath.Join("go", "src", "github.com", "username", "package")), false},
        {th.Path("gotwo"), th.Path(filepath.Join("gotwo", "src", "github.com", "username", "package")), false},
        {th.Path(filepath.Join("gothree", "sep")),
            th.Path(filepath.Join("gothree", "sep", "src", "github.com", "username", "package")), false},
        {"", th.Path(filepath.Join("code", "src", "github.com", "username", "package")), true},
    }

    for _, tc := range testcases {
        GOPATH, err := ctx.detectGOPATH(tc.path)
        if tc.err && err == nil {
            t.Error("expected error but got none")
        }
        if GOPATH != tc.GOPATH {
            t.Errorf("expected GOPATH to be %s, got %s", tc.GOPATH, GOPATH)
        }
    }
}

func TestDepCachedir(t *testing.T) {
    h := test.NewHelper(t)
    defer h.Cleanup()

    h.TempDir("cache")
    // Create the directory for default cachedir location.
    h.TempDir(filepath.Join("go", "pkg", "dep"))

    testCachedir := h.Path("cache")
    gopath := h.Path("go")
    discardLgr := discardLogger()

    cases := []struct {
        cachedir     string
        wantCachedir string
    }{
        // If `Cachedir` is not set in the context, it should use `$GOPATH/pkg/dep`.
        {cachedir: "", wantCachedir: h.Path(filepath.Join("go", "pkg", "dep"))},
        // If `Cachedir` is set in the context, it should use that.
        {cachedir: testCachedir, wantCachedir: testCachedir},
    }

    for _, c := range cases {
        ctx := &Ctx{
            GOPATH:   gopath,
            Cachedir: c.cachedir,
            Out:      discardLgr,
            Err:      discardLgr,
        }
        sm, err := ctx.SourceManager()
        h.Must(err)
        defer sm.Release()

        if sm.Cachedir() != c.wantCachedir {
            t.Errorf("expected cachedir to be %s, got %s", c.wantCachedir, sm.Cachedir())
        }
    }
}