package selfupdate import ( "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "github.com/blang/semver" ) func setupTestBinary(name ...string) { var options []string var output string if len(name) == 0 { options = []string{"build", "./testdata/github-release-test/"} } else { output = name[0] if runtime.GOOS == "windows" { output += ".exe" } options = []string{"build", "-o", output, "./testdata/github-release-test/"} } if err := exec.Command("go", options...).Run(); err != nil { panic(err) } } func teardownTestBinary(name ...string) { var bin string if len(name) == 0 { bin = "github-release-test" } else { bin = name[0] } if runtime.GOOS == "windows" { bin += ".exe" } if err := os.Remove(bin); err != nil { panic(err) } } func TestUpdateCommand(t *testing.T) { if testing.Short() { t.Skip("skip tests in short mode.") } for _, slug := range []string{ "rhysd-test/test-release-zip", "rhysd-test/test-release-tar", "rhysd-test/test-release-gzip", "rhysd-test/test-release-tar-xz", "rhysd-test/test-release-xz", "rhysd-test/test-release-contain-version", } { t.Run(slug, func(t *testing.T) { setupTestBinary() defer teardownTestBinary() latest := semver.MustParse("1.2.3") prev := semver.MustParse("1.2.2") rel, err := UpdateCommand("github-release-test", prev, slug) if err != nil { t.Fatal(err) } if rel.Version.NE(latest) { t.Error("Version is not latest", rel.Version) } 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) } }) } } func TestUpdateWithDifferentBinaryName(t *testing.T) { setupTestBinary("gh-release-test") defer teardownTestBinary("gh-release-test") latest := semver.MustParse("1.2.3") prev := semver.MustParse("1.2.2") _, err := UpdateCommand("gh-release-test", prev, "rhysd-test/test-release-zip") if err == nil { t.Fatal("Error should occur for broken package") } if !strings.Contains(err.Error(), "the command is not found") { t.Fatal("Unexpected error:", err) } up, err := NewUpdater(Config{BinaryName: "github-release-test", Filters: []string{"github-release-test"}}) if err != nil { t.Fatal(err) } rel, err := up.UpdateCommand("gh-release-test", 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) } bytes, err := exec.Command(filepath.FromSlash("./gh-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) } } func TestUpdateViaSymlink(t *testing.T) { if testing.Short() { t.Skip("skip tests in short mode.") } if runtime.GOOS == "windows" && os.Getenv("APPVEYOR") == "" { t.Skip("skipping because creating symlink on windows requires the root privilege") } setupTestBinary() defer teardownTestBinary() exePath := "github-release-test" symPath := "github-release-test-sym" if runtime.GOOS == "windows" { exePath = "github-release-test.exe" symPath = "github-release-test-sym.exe" } if err := os.Symlink(exePath, symPath); err != nil { t.Fatal(err) } defer os.Remove(symPath) latest := semver.MustParse("1.2.3") prev := semver.MustParse("1.2.2") rel, err := UpdateCommand(symPath, 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(symPath) if err != nil { t.Fatal(err) } if s.Mode()&os.ModeSymlink == 0 { t.Fatalf("%s is not a symlink.", symPath) } p, err := filepath.EvalSymlinks(symPath) if err != nil { t.Fatal(err) } if p != exePath { t.Fatal("Created symlink no loger points the executable:", p) } } func TestUpdateBrokenSymlinks(t *testing.T) { if runtime.GOOS == "windows" && os.Getenv("APPVEYOR") == "" { t.Skip("skipping because creating symlink on windows requires the root privilege") } // unknown-xxx -> unknown-yyy -> {not existing} xxx := "unknown-xxx" yyy := "unknown-yyy" if runtime.GOOS == "windows" { xxx = "unknown-xxx.exe" yyy = "unknown-yyy.exe" } if err := os.Symlink("not-existing", yyy); err != nil { t.Fatal(err) } defer os.Remove(yyy) if err := os.Symlink(yyy, xxx); err != nil { t.Fatal(err) } defer os.Remove(xxx) v := semver.MustParse("1.2.2") for _, p := range []string{yyy, xxx} { _, err := UpdateCommand(p, 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") fake := filepath.FromSlash("./testdata/fake-executable") rel, err := UpdateCommand(fake, v, "rhysd/misc") if err != nil { t.Fatal("No release should not make an error:", err) } if rel.Version.NE(v) { t.Error("No release should return the current version as the latest:", rel.Version) } if rel.URL != "" { t.Error("Browse URL should be empty when no release found:", rel.URL) } if rel.AssetURL != "" { t.Error("Asset URL should be empty when no release found:", rel.AssetURL) } if rel.ReleaseNotes != "" { t.Error("Release notes should be empty when no release found:", rel.ReleaseNotes) } } func TestCurrentIsTheLatest(t *testing.T) { if testing.Short() { t.Skip("skip tests in short mode.") } setupTestBinary() defer teardownTestBinary() v := semver.MustParse("1.2.3") rel, err := UpdateCommand("github-release-test", v, "rhysd-test/test-release-zip") if err != nil { t.Fatal(err) } if rel.Version.NE(v) { t.Error("v1.2.3 should be the latest:", rel.Version) } if rel.URL == "" { t.Error("Browse URL should not be empty when release found:", rel.URL) } if rel.AssetURL == "" { t.Error("Asset URL should not be empty when release found:", rel.AssetURL) } if rel.ReleaseNotes == "" { t.Error("Release notes should not be empty when release found:", rel.ReleaseNotes) } } func TestBrokenBinaryUpdate(t *testing.T) { if testing.Short() { t.Skip("skip tests in short mode.") } fake := filepath.FromSlash("./testdata/fake-executable") _, err := UpdateCommand(fake, semver.MustParse("1.2.2"), "rhysd-test/test-incorrect-release") if err == nil { t.Fatal("Error should occur for broken package") } if !strings.Contains(err.Error(), "Failed to uncompress .tar.gz file") { t.Fatal("Unexpected error:", err) } } func TestInvalidSlugForUpdate(t *testing.T) { fake := filepath.FromSlash("./testdata/fake-executable") _, err := UpdateCommand(fake, semver.MustParse("1.0.0"), "rhysd/") if err == nil { t.Fatal("Unknown repo should cause an error") } if !strings.Contains(err.Error(), "Invalid slug format") { t.Fatal("Unexpected error:", err) } } func TestInvalidAssetURL(t *testing.T) { err := UpdateTo("https://github.com/rhysd/non-existing-repo/releases/download/v1.2.3/foo.zip", "foo") if err == nil { t.Fatal("Error should occur for URL not found") } if !strings.Contains(err.Error(), "Failed to download a release file") { t.Fatal("Unexpected error:", err) } } func TestBrokenAsset(t *testing.T) { asset := "https://github.com/rhysd-test/test-incorrect-release/releases/download/invalid/broken-zip.zip" err := UpdateTo(asset, "foo") if err == nil { t.Fatal("Error should occur for URL not found") } if !strings.Contains(err.Error(), "Failed to uncompress zip file") { t.Fatal("Unexpected error:", err) } } func TestBrokenGitHubEnterpriseURL(t *testing.T) { up, err := NewUpdater(Config{APIToken: "hogehoge", EnterpriseBaseURL: "https://example.com"}) if err != nil { t.Fatal(err) } err = up.UpdateTo(&Release{AssetURL: "https://example.com"}, "foo") if err == nil { t.Fatal("Invalid GitHub Enterprise base URL should raise an error") } if !strings.Contains(err.Error(), "Failed to call GitHub Releases API for getting an asset") { t.Error("Unexpected error occurred:", err) } } func TestUpdateFromGitHubEnterprise(t *testing.T) { token := os.Getenv("GITHUB_ENTERPRISE_TOKEN") base := os.Getenv("GITHUB_ENTERPRISE_BASE_URL") repo := os.Getenv("GITHUB_ENTERPRISE_REPO") if token == "" { t.Skip("because token for GHE is not found") } if base == "" { t.Skip("because base URL for GHE is not found") } if repo == "" { t.Skip("because repo slug for GHE is not found") } setupTestBinary() defer teardownTestBinary() up, err := NewUpdater(Config{APIToken: token, EnterpriseBaseURL: base}) if err != nil { t.Fatal(err) } latest := semver.MustParse("1.2.3") prev := semver.MustParse("1.2.2") rel, err := up.UpdateCommand("github-release-test", prev, repo) if err != nil { t.Fatal(err) } if rel.Version.NE(latest) { t.Error("Version is not latest", rel.Version) } 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) } } func TestUpdateFromGitHubPrivateRepo(t *testing.T) { token := os.Getenv("GITHUB_PRIVATE_TOKEN") if token == "" { t.Skip("because GITHUB_PRIVATE_TOKEN is not set") } setupTestBinary() defer teardownTestBinary() up, err := NewUpdater(Config{APIToken: token}) if err != nil { t.Fatal(err) } latest := semver.MustParse("1.2.3") prev := semver.MustParse("1.2.2") rel, err := up.UpdateCommand("github-release-test", prev, "rhysd/private-release-test") if err != nil { t.Fatal(err) } if rel.Version.NE(latest) { t.Error("Version is not latest", rel.Version) } 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) } }