From 3a2ffd274f7b47bc6a3b37afc4dad4280744b5f5 Mon Sep 17 00:00:00 2001 From: rhysd Date: Wed, 3 Jan 2018 23:01:33 +0900 Subject: [PATCH] consider the case where executable is symlink --- selfupdate/update.go | 12 ++++++ selfupdate/update_test.go | 77 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/selfupdate/update.go b/selfupdate/update.go index 7b93f70..29f9eff 100644 --- a/selfupdate/update.go +++ b/selfupdate/update.go @@ -44,6 +44,18 @@ func UpdateCommand(cmdPath string, current semver.Version, slug string) (*Releas cmdPath = cmdPath + ".exe" } + stat, err := os.Lstat(cmdPath) + if err != nil { + return nil, fmt.Errorf("Failed to stat '%s'. File may not exist: %s", cmdPath, err) + } + if stat.Mode()&os.ModeSymlink != 0 { + p, err := filepath.EvalSymlinks(cmdPath) + if err != nil { + return nil, fmt.Errorf("Failed to resolve symlink '%s' for executable: %s", cmdPath, err) + } + cmdPath = p + } + rel, ok, err := DetectLatest(slug) if err != nil { return nil, err diff --git a/selfupdate/update_test.go b/selfupdate/update_test.go index 6f4f9c1..c1aa07f 100644 --- a/selfupdate/update_test.go +++ b/selfupdate/update_test.go @@ -63,6 +63,83 @@ func TestUpdateCommand(t *testing.T) { } } +func TestUpdateViaSymlink(t *testing.T) { + setupTestBinary() + defer teardownTestBinary() + if err := os.Symlink("github-release-test", "github-release-test-sym"); err != nil { + t.Fatal(err) + } + defer os.Remove("github-release-test-sym") + + latest := semver.MustParse("1.2.3") + prev := semver.MustParse("1.2.2") + rel, err := UpdateCommand("github-release-test-sym", prev, "rhysd-test/test-release-zip") + if err != nil { + t.Fatal(err) + } + if rel.Version.NE(latest) { + t.Error("Version is not latest", rel.Version) + } + + // Test not symbolic link, but actual physical executable + bytes, err := exec.Command(filepath.FromSlash("./github-release-test")).Output() + if err != nil { + t.Fatal("Failed to run test binary after update:", err) + } + out := string(bytes) + if out != "v1.2.3\n" { + t.Error("Output from test binary after update is unexpected:", out) + } + + s, err := os.Lstat("github-release-test-sym") + if err != nil { + t.Fatal(err) + } + if s.Mode()&os.ModeSymlink == 0 { + t.Fatalf("%s is not a symlink.", "github-release-test-sym") + } + p, err := filepath.EvalSymlinks("github-release-test-sym") + if err != nil { + t.Fatal(err) + } + if p != "github-release-test" { + t.Fatalf("Created symlink no loger points the executable") + } +} + +func TestUpdateBrokenSymlinks(t *testing.T) { + // unknown-xxx -> unknown-yyy -> {not existing} + if err := os.Symlink("not-existing", "unknown-yyy"); err != nil { + t.Fatal(err) + } + defer os.Remove("unknown-yyy") + if err := os.Symlink("unknown-yyy", "unknown-xxx"); err != nil { + t.Fatal(err) + } + defer os.Remove("unknown-xxx") + + v := semver.MustParse("1.2.2") + for _, p := range []string{"unknown-yyy", "unknown-xxx"} { + _, err := UpdateCommand("unknown-yyy", v, "owner/repo") + if err == nil { + t.Fatal("Error should occur for unlinked symlink", p) + } + if !strings.Contains(err.Error(), "Failed to resolve symlink") { + t.Fatal("Unexpected error for broken symlink", p, err) + } + } +} + +func TestNotExistingCommandPath(t *testing.T) { + _, err := UpdateCommand("not-existing-command-path", semver.MustParse("1.2.2"), "owner/repo") + if err == nil { + t.Fatal("Not existing command path should cause an error") + } + if !strings.Contains(err.Error(), "File may not exist") { + t.Fatal("Unexpected error for not existing command path", err) + } +} + func TestNoReleaseFoundForUpdate(t *testing.T) { v := semver.MustParse("1.0.0") rel, err := UpdateCommand("foo", v, "rhysd/misc")