| // Copyright 2016 The Fuchsia 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 selfupdate |
| |
| import ( |
| "bytes" |
| "errors" |
| "io" |
| "net/http" |
| "net/http/httptest" |
| "os" |
| "strings" |
| "testing" |
| |
| "go.fuchsia.dev/jiri/gitutil" |
| "go.fuchsia.dev/jiri/jiritest" |
| "go.fuchsia.dev/jiri/jiritest/xtest" |
| ) |
| |
| func TestMain(m *testing.M) { |
| jiritest.IsolateGitEnv() |
| os.Exit(m.Run()) |
| } |
| |
| func TestGetCurrentCommit(t *testing.T) { |
| t.Parallel() |
| ctx := xtest.NewContext(t) |
| repoDir := t.TempDir() |
| |
| g := gitutil.New(ctx, gitutil.RootDirOpt(repoDir)) |
| if err := g.Init(ctx, repoDir); err != nil { |
| t.Fatal(err) |
| } |
| if err := g.Config(ctx, "user.email", "alice@example.com"); err != nil { |
| t.Fatal(err) |
| } |
| if err := g.Config(ctx, "user.name", "Alice"); err != nil { |
| t.Fatal(err) |
| } |
| if err := g.CommitWithMessage(ctx, "initial commit"); err != nil { |
| t.Fatal(err) |
| } |
| want, err := g.CurrentRevision(ctx) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| got, err := getCurrentCommit(ctx, repoDir) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if got != want { |
| t.Errorf("wrong commit, want: %s, got: %s", want, got) |
| } |
| } |
| |
| func TestHasPrebuilt(t *testing.T) { |
| t.Parallel() |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.WriteHeader(http.StatusOK) |
| })) |
| defer ts.Close() |
| |
| _, err := downloadBinary(t.Context(), ts.URL, "abc123") |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestDoesNotHavePrebuilt(t *testing.T) { |
| t.Parallel() |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.WriteHeader(http.StatusNotFound) |
| })) |
| defer ts.Close() |
| |
| _, err := downloadBinary(t.Context(), ts.URL, "abc123") |
| if err != updateNotAvailableErr { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestDownloadBinary(t *testing.T) { |
| t.Parallel() |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.Header().Set("Content-Type", "application/octet-stream") |
| b := bytes.NewBuffer([]byte("jiri")) |
| b.WriteTo(w) |
| })) |
| defer ts.Close() |
| |
| b, err := downloadBinary(t.Context(), ts.URL, "abc123") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if want, got := []byte("jiri"), b; !bytes.Equal(want, got) { |
| t.Errorf("wrong file content, want: %s, got: %s\n", want, got) |
| } |
| } |
| |
| func TestDownloadBinaryHTTPError(t *testing.T) { |
| t.Parallel() |
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| http.Error(w, "internal server error", http.StatusInternalServerError) |
| })) |
| defer ts.Close() |
| |
| _, err := downloadBinary(t.Context(), ts.URL, "abc123") |
| if err == nil { |
| t.Fatal("expected error, got nil") |
| } |
| errMsg := err.Error() |
| if !strings.Contains(errMsg, "HTTP request failed") { |
| t.Errorf("error message missing 'HTTP request failed', got: %s", errMsg) |
| } |
| if !strings.Contains(errMsg, "500") { |
| t.Errorf("error message missing status '500', got: %s", errMsg) |
| } |
| if !strings.Contains(errMsg, "internal server error") { |
| t.Errorf("error message missing body 'internal server error', got: %s", errMsg) |
| } |
| } |
| |
| func TestMaybeTruncateString(t *testing.T) { |
| t.Parallel() |
| |
| tests := []struct { |
| name string |
| input string |
| maxLen int |
| want string |
| }{ |
| { |
| name: "shorter than maxLen", |
| input: "short string", |
| maxLen: 50, |
| want: "short string", |
| }, |
| { |
| name: "exact maxLen", |
| input: "exact", |
| maxLen: 5, |
| want: "exact", |
| }, |
| { |
| name: "longer than maxLen", |
| input: "0123456789", |
| maxLen: 6, |
| want: "012345...", |
| }, |
| { |
| name: "multibyte unicode characters truncated", |
| input: "Hello, δΈη ππ", |
| maxLen: 9, |
| want: "Hello, δΈη...", |
| }, |
| { |
| name: "multibyte unicode characters shorter than maxLen", |
| input: "Hello, δΈη ππ", |
| maxLen: 12, |
| want: "Hello, δΈη ππ", |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.name, func(t *testing.T) { |
| got := maybeTruncateString(tc.input, tc.maxLen) |
| if got != tc.want { |
| t.Errorf("maybeTruncateString(%q, %d) = %q, want %q", tc.input, tc.maxLen, got, tc.want) |
| } |
| }) |
| } |
| } |
| |
| type errorReader struct{} |
| |
| func (errorReader) Read(p []byte) (n int, err error) { |
| return 0, errors.New("read failure") |
| } |
| |
| func (errorReader) Close() error { |
| return nil |
| } |
| |
| func TestCheckHTTPResponseErrors(t *testing.T) { |
| t.Parallel() |
| |
| tests := []struct { |
| name string |
| response *http.Response |
| errContains []string |
| }{ |
| { |
| name: "StatusOK returns nil", |
| response: &http.Response{ |
| StatusCode: http.StatusOK, |
| Status: "200 OK", |
| Body: io.NopCloser(strings.NewReader("ok")), |
| }, |
| }, |
| { |
| name: "StatusError with body", |
| response: &http.Response{ |
| StatusCode: http.StatusServiceUnavailable, |
| Status: "503 Service Unavailable", |
| Body: io.NopCloser(strings.NewReader("rate limit exceeded")), |
| }, |
| errContains: []string{"HTTP request failed", "503 Service Unavailable", "rate limit exceeded"}, |
| }, |
| { |
| name: "StatusError with long body", |
| response: &http.Response{ |
| StatusCode: http.StatusServiceUnavailable, |
| Status: "503 Service Unavailable", |
| Body: io.NopCloser(strings.NewReader(strings.Repeat("π", 4000))), |
| }, |
| errContains: []string{"HTTP request failed", "503 Service Unavailable", "π..."}, |
| }, |
| { |
| name: "StatusError with empty body", |
| response: &http.Response{ |
| StatusCode: http.StatusBadRequest, |
| Status: "400 Bad Request", |
| Body: io.NopCloser(strings.NewReader("")), |
| }, |
| errContains: []string{"HTTP request failed", "400 Bad Request"}, |
| }, |
| { |
| name: "StatusError with failing body reader", |
| response: &http.Response{ |
| StatusCode: http.StatusInternalServerError, |
| Status: "500 Internal Server Error", |
| Body: errorReader{}, |
| }, |
| errContains: []string{"HTTP request failed", "failed to read body", "read failure"}, |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.name, func(t *testing.T) { |
| err := checkHTTPResponseErrors(tc.response) |
| if len(tc.errContains) == 0 { |
| if err != nil { |
| t.Fatalf("expected nil error, got: %v", err) |
| } |
| return |
| } |
| if err == nil { |
| t.Fatal("expected error, got nil") |
| } |
| errMsg := err.Error() |
| for _, substr := range tc.errContains { |
| if !strings.Contains(errMsg, substr) { |
| t.Errorf("error message %q missing expected substring %q", errMsg, substr) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestUpdateExecutable(t *testing.T) { |
| t.Parallel() |
| content := []byte("old") |
| |
| f, err := os.CreateTemp(t.TempDir(), "jiri") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if _, err := f.Write(content); err != nil { |
| t.Fatal(err) |
| } |
| if err := f.Close(); err != nil { |
| t.Fatal(err) |
| } |
| |
| if err := updateExecutable(f.Name(), []byte("new")); err != nil { |
| t.Fatal(err) |
| } |
| |
| b, err := os.ReadFile(f.Name()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if want, got := []byte("new"), b; !bytes.Equal(want, got) { |
| t.Errorf("wrong file content, want: %s, got: %s\n", want, got) |
| } |
| } |