Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions goutil/goutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ func ResolvePackage(path string, mode build.ImportMode) (pkg *build.Package, err
return nil, errors.New("cannot resolve empty string")
}

switch path[0] {
case '/':
switch {
case isAbsPath(path):
pkg, err = build.ImportDir(path, mode)
case '.':
case path[0] == '.':
path, err = filepath.Abs(path)
if err != nil {
return nil, err
Expand All @@ -88,6 +88,29 @@ func ResolvePackage(path string, mode build.ImportMode) (pkg *build.Package, err
return pkg, err
}

// isAbsPath reports whether p is an absolute path under either Unix or Windows
// conventions. filepath.IsAbs only knows the host's convention, so on Unix it
// rejects `C:\dir` and on Windows it rejects `/dir`.
func isAbsPath(p string) bool {
if filepath.IsAbs(p) || strings.HasPrefix(p, "/") {
return true
}
if strings.HasPrefix(p, `\\`) { // UNC path
return true
}
// Drive letter, e.g. C:\dir or C:/dir
return len(p) >= 3 && p[1] == ':' && (p[2] == '\\' || p[2] == '/') &&
(('A' <= p[0] && p[0] <= 'Z') || ('a' <= p[0] && p[0] <= 'z'))
}

// dirName returns the name of the directory containing file, accepting both
// separators so a Windows path resolves on any host: path.Dir is slash-only and
// returns "." for `C:\src\pkg\file.go`. A literal backslash in a Unix file
// name is treated as a separator, which Go source paths never rely on.
func dirName(file string) string {
return path.Base(path.Dir(strings.ReplaceAll(file, `\`, "/")))
}

// ResolveWildcard finds all subpackages in the "example/..." format. The
// "/vendor/" directory will be ignored.
func ResolveWildcard(path string, mode build.ImportMode) ([]*build.Package, error) {
Expand Down Expand Up @@ -198,7 +221,7 @@ func ResolveImport(file, pkgName string) (string, error) {

r, ok := imports[pkgName]
if !ok {
currentPkg := path.Base(path.Dir(file))
currentPkg := dirName(file)
if pkgName == currentPkg {
r = "."
}
Expand Down
48 changes: 48 additions & 0 deletions goutil/goutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,51 @@ func TestTagName(t *testing.T) {

})
}

func TestIsAbsPath(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"/home/dev/proj", true},
{"/", true},
{`C:\Users\dev\proj`, true},
{"C:/Users/dev/proj", true},
{`c:\users\dev`, true},
{`\\server\share\proj`, true},
{"github.com/teamwork/utils", false},
{"./proj", false},
{"proj", false},
{"", false},
{"C:", false},
{"1:/nope", false},
}

for _, tc := range cases {
t.Run(tc.in, func(t *testing.T) {
if out := isAbsPath(tc.in); out != tc.want {
t.Errorf("\nout: %v\nwant: %v\n", out, tc.want)
}
})
}
}

func TestDirName(t *testing.T) {
cases := []struct {
in, want string
}{
{"/home/dev/go/src/example/pkg/file.go", "pkg"},
{`C:\Users\dev\go\src\example\pkg\file.go`, "pkg"},
{"C:/Users/dev/go/src/example/pkg/file.go", "pkg"},
{"pkg/file.go", "pkg"},
{"file.go", "."},
}

for _, tc := range cases {
t.Run(tc.in, func(t *testing.T) {
if out := dirName(tc.in); out != tc.want {
t.Errorf("\nout: %#v\nwant: %#v\n", out, tc.want)
}
})
}
}
31 changes: 26 additions & 5 deletions httputilx/httputilx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,36 @@ func chunk(s string) string {

// TODO: better to not depend on interwebz...
func TestFetch(t *testing.T) {
// Served locally rather than from httpbin.org: the external service returns
// a 503 often enough to fail the build for reasons unrelated to any change.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/html":
_, _ = w.Write([]byte("<html><body>hello</body></html>"))
case "/status/400":
w.WriteHeader(http.StatusBadRequest)
case "/status/500":
w.WriteHeader(http.StatusInternalServerError)
case "/status/418":
w.WriteHeader(http.StatusTeapot)
_, _ = w.Write([]byte("teapot"))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()

cases := []struct {
in, want, wantErr string
}{
{"http://example.com", "<html", ""},
{"http://fairly-certain-this-doesnt-exist-asdasd12g1ghdfddd.com", "", "cannot download"},
{"http://httpbin.org/status/400", "", "400"},
{"http://httpbin.org/status/500", "", "500"},
{srv.URL + "/html", "<html", ""},
// Port 1 is never listening, so this fails to connect without needing
// DNS for a domain that does not exist.
{"http://127.0.0.1:1", "", "cannot download"},
{srv.URL + "/status/400", "", "400"},
{srv.URL + "/status/500", "", "500"},
// Make sure we return the body as well.
{"http://httpbin.org/status/418", "teapot", "418"},
{srv.URL + "/status/418", "teapot", "418"},
}

for _, tc := range cases {
Expand Down