internal/span: handle empty paths correctly

Change-Id: I3f411c5e27bcada26a756485a3b9de3514ac7955
Reviewed-on: https://go-review.googlesource.com/c/tools/+/181677
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/span/uri.go b/internal/span/uri.go
index 3263437..a273c01 100644
--- a/internal/span/uri.go
+++ b/internal/span/uri.go
@@ -8,6 +8,7 @@
 	"fmt"
 	"net/url"
 	"os"
+	"path"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -30,6 +31,9 @@
 }
 
 func filename(uri URI) (string, error) {
+	if uri == "" {
+		return "", nil
+	}
 	u, err := url.ParseRequestURI(string(uri))
 	if err != nil {
 		return "", err
@@ -56,28 +60,49 @@
 }
 
 func CompareURI(a, b URI) int {
-	if a == b {
+	if equalURI(a, b) {
 		return 0
 	}
-	// If we have the same URI basename, we may still have the same file URIs.
-	fa := a.Filename()
-	fb := b.Filename()
-	if strings.EqualFold(filepath.Base(fa), filepath.Base(fb)) {
-		// Stat the files to check if they are equal.
-		if infoa, err := os.Stat(fa); err == nil {
-			if infob, err := os.Stat(fb); err == nil {
-				if os.SameFile(infoa, infob) {
-					return 0
-				}
-			}
-		}
+	if a < b {
+		return -1
 	}
-	return strings.Compare(fa, fb)
+	return 1
+}
+
+func equalURI(a, b URI) bool {
+	if a == b {
+		return true
+	}
+	// If we have the same URI basename, we may still have the same file URIs.
+	if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
+		return false
+	}
+	fa, err := filename(a)
+	if err != nil {
+		return false
+	}
+	fb, err := filename(b)
+	if err != nil {
+		return false
+	}
+	// Stat the files to check if they are equal.
+	infoa, err := os.Stat(filepath.FromSlash(fa))
+	if err != nil {
+		return false
+	}
+	infob, err := os.Stat(filepath.FromSlash(fb))
+	if err != nil {
+		return false
+	}
+	return os.SameFile(infoa, infob)
 }
 
 // FileURI returns a span URI for the supplied file path.
 // It will always have the file scheme.
 func FileURI(path string) URI {
+	if path == "" {
+		return ""
+	}
 	// Handle standard library paths that contain the literal "$GOROOT".
 	// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
 	const prefix = "$GOROOT"
diff --git a/internal/span/uri_test.go b/internal/span/uri_test.go
index 7b7c8b9..36dd199 100644
--- a/internal/span/uri_test.go
+++ b/internal/span/uri_test.go
@@ -17,6 +17,7 @@
 // functions filepath.ToSlash and filepath.FromSlash do not need testing.
 func TestURI(t *testing.T) {
 	for _, test := range []string{
+		``,
 		`C:/Windows/System32`,
 		`C:/Go/src/bob.go`,
 		`c:/Go/src/bob.go`,
@@ -25,16 +26,18 @@
 	} {
 		testPath := filepath.FromSlash(test)
 		expectPath := testPath
-		if test[0] == '/' {
+		if len(test) > 0 && test[0] == '/' {
 			if abs, err := filepath.Abs(expectPath); err == nil {
 				expectPath = abs
 			}
 		}
 		expectURI := filepath.ToSlash(expectPath)
-		if expectURI[0] != '/' {
-			expectURI = "/" + expectURI
+		if len(expectURI) > 0 {
+			if expectURI[0] != '/' {
+				expectURI = "/" + expectURI
+			}
+			expectURI = "file://" + expectURI
 		}
-		expectURI = "file://" + expectURI
 		uri := span.FileURI(testPath)
 		if expectURI != string(uri) {
 			t.Errorf("ToURI: expected %s, got %s", expectURI, uri)