-
Notifications
You must be signed in to change notification settings - Fork 4
[draft] DO NOT MERGE: rayapp test prototype #372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
9c9a162
1729794
7e70483
78b1840
87f5ddb
17f39f0
aca755f
eb85bac
e7c49e7
291723e
f5c9cf0
7370b08
9f5de30
5ba726f
b4bf53a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package rayapp | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "errors" | ||
| "fmt" | ||
| "os/exec" | ||
| ) | ||
|
|
||
| var errAnyscaleNotInstalled = errors.New("anyscale is not installed") | ||
|
|
||
| func isAnyscaleInstalled() bool { | ||
| _, err := exec.LookPath("anyscale") | ||
| return err == nil | ||
| } | ||
|
|
||
| // RunAnyscaleCLI runs the anyscale CLI with the given arguments. | ||
| func RunAnyscaleCLI(args []string) (string, error) { | ||
| if !isAnyscaleInstalled() { | ||
| return "", errAnyscaleNotInstalled | ||
| } | ||
|
|
||
| cmd := exec.Command("anyscale", args...) | ||
|
|
||
| var stdout, stderr bytes.Buffer | ||
| cmd.Stdout = &stdout | ||
| cmd.Stderr = &stderr | ||
|
|
||
| err := cmd.Run() | ||
| if err != nil { | ||
| return "", fmt.Errorf("anyscale error: %v\nstderr: %s", err, stderr.String()) | ||
| } | ||
|
|
||
| return stdout.String(), nil | ||
| } | ||
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,110 @@ | ||||||||||||||||||||||||||||||
| package rayapp | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||
| "testing" | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // setupMockAnyscale creates a mock anyscale script and returns a cleanup function. | ||||||||||||||||||||||||||||||
| func setupMockAnyscale(t *testing.T, script string) { | ||||||||||||||||||||||||||||||
| t.Helper() | ||||||||||||||||||||||||||||||
| tmp := t.TempDir() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if script != "" { | ||||||||||||||||||||||||||||||
| mockScript := tmp + "/anyscale" | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For constructing file paths, it's better to use
Suggested change
|
||||||||||||||||||||||||||||||
| if err := os.WriteFile(mockScript, []byte(script), 0755); err != nil { | ||||||||||||||||||||||||||||||
| t.Fatalf("failed to create mock script: %v", err) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| origPath := os.Getenv("PATH") | ||||||||||||||||||||||||||||||
| t.Cleanup(func() { os.Setenv("PATH", origPath) }) | ||||||||||||||||||||||||||||||
| os.Setenv("PATH", tmp) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| func TestRunAnyscaleCLI(t *testing.T) { | ||||||||||||||||||||||||||||||
| tests := []struct { | ||||||||||||||||||||||||||||||
| name string | ||||||||||||||||||||||||||||||
| script string | ||||||||||||||||||||||||||||||
| args []string | ||||||||||||||||||||||||||||||
| wantErr error | ||||||||||||||||||||||||||||||
| wantSubstr string | ||||||||||||||||||||||||||||||
| }{ | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "anyscale not installed", | ||||||||||||||||||||||||||||||
| script: "", // empty PATH, no script | ||||||||||||||||||||||||||||||
| args: []string{"--version"}, | ||||||||||||||||||||||||||||||
| wantErr: errAnyscaleNotInstalled, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "success", | ||||||||||||||||||||||||||||||
| script: "#!/bin/sh\necho \"output: $@\"", | ||||||||||||||||||||||||||||||
| args: []string{"service", "deploy"}, | ||||||||||||||||||||||||||||||
| wantSubstr: "output: service deploy", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "empty args", | ||||||||||||||||||||||||||||||
| script: "#!/bin/sh\necho \"help\"", | ||||||||||||||||||||||||||||||
| args: []string{}, | ||||||||||||||||||||||||||||||
| wantSubstr: "help", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "command fails with stderr", | ||||||||||||||||||||||||||||||
| script: "#!/bin/sh\necho \"error msg\" >&2; exit 1", | ||||||||||||||||||||||||||||||
| args: []string{"deploy"}, | ||||||||||||||||||||||||||||||
| wantSubstr: "error msg", | ||||||||||||||||||||||||||||||
| wantErr: errors.New("anyscale error"), | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| for _, tt := range tests { | ||||||||||||||||||||||||||||||
| t.Run(tt.name, func(t *testing.T) { | ||||||||||||||||||||||||||||||
| setupMockAnyscale(t, tt.script) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| output, err := RunAnyscaleCLI(tt.args) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if tt.wantErr != nil { | ||||||||||||||||||||||||||||||
| if err == nil { | ||||||||||||||||||||||||||||||
| t.Fatal("expected error, got nil") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if errors.Is(tt.wantErr, errAnyscaleNotInstalled) { | ||||||||||||||||||||||||||||||
| if !errors.Is(err, errAnyscaleNotInstalled) { | ||||||||||||||||||||||||||||||
| t.Errorf("expected errAnyscaleNotInstalled, got: %v", err) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } else if !strings.Contains(err.Error(), tt.wantErr.Error()) { | ||||||||||||||||||||||||||||||
| t.Errorf("error %q should contain %q", err.Error(), tt.wantErr.Error()) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+188
to
+194
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error checking logic is a bit complex and can be simplified for better readability and maintainability. Specifically,
Suggested change
|
||||||||||||||||||||||||||||||
| // For error cases, also check wantSubstr against error message | ||||||||||||||||||||||||||||||
| if tt.wantSubstr != "" && !strings.Contains(err.Error(), tt.wantSubstr) { | ||||||||||||||||||||||||||||||
| t.Errorf("error %q should contain %q", err.Error(), tt.wantSubstr) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| t.Fatalf("unexpected error: %v", err) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if tt.wantSubstr != "" && !strings.Contains(output, tt.wantSubstr) { | ||||||||||||||||||||||||||||||
| t.Errorf("output %q should contain %q", output, tt.wantSubstr) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| func TestIsAnyscaleInstalled(t *testing.T) { | ||||||||||||||||||||||||||||||
| t.Run("not installed", func(t *testing.T) { | ||||||||||||||||||||||||||||||
| setupMockAnyscale(t, "") | ||||||||||||||||||||||||||||||
| if isAnyscaleInstalled() { | ||||||||||||||||||||||||||||||
| t.Error("should return false when not in PATH") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| t.Run("installed", func(t *testing.T) { | ||||||||||||||||||||||||||||||
| setupMockAnyscale(t, "#!/bin/sh\necho mock") | ||||||||||||||||||||||||||||||
| if !isAnyscaleInstalled() { | ||||||||||||||||||||||||||||||
| t.Error("should return true when in PATH") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error formatting could be improved for better diagnostics and to follow modern Go practices. Using
%wto wrap the error allows for inspection of the error chain witherrors.Isorerrors.As. Also, including a newline\nin the error message can make logs harder to parse. It's generally better to have single-line error messages.