| // Copyright 2025 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package main |
| |
| import ( |
| "flag" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| ) |
| |
| // mockLookupEnv creates a lookup function that reads from the provided map. |
| func mockLookupEnv(env map[string]string) func(key string) (string, bool) { |
| return func(key string) (string, bool) { |
| val, ok := env[key] |
| return val, ok |
| } |
| } |
| |
| // newDefaultConfigForTest creates a mainConfig struct and populates it with default values |
| // by registering flags on a new FlagSet and then parsing empty arguments. |
| func newDefaultConfigForTest(env map[string]string) *mainConfig { |
| fs := flag.NewFlagSet("default", flag.ContinueOnError) |
| cfg := &mainConfig{} |
| registerRsproxyFlags(fs, cfg) |
| // Parse empty arguments to ensure default values are applied to cfg. |
| fs.Parse([]string{}) |
| // Parse environment variables. |
| parseFromEnv(fs, mockLookupEnv(env)) |
| return cfg |
| } |
| |
| func TestParseUnset(t *testing.T) { |
| fs := flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg := newDefaultConfigForTest(nil) |
| registerRsproxyFlags(fs, cfg) |
| |
| if err := FlagsToConfig(fs, cfg, []string{}, mockLookupEnv(nil)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig := newDefaultConfigForTest(nil) |
| |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestParseSet(t *testing.T) { |
| // Test environment variable overriding command-line argument for upload_requests_file. |
| env := map[string]string{ |
| "RS_upload_requests_file": "env_file.txt", |
| } |
| fs := flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg := newDefaultConfigForTest(env) |
| registerRsproxyFlags(fs, cfg) |
| |
| args := []string{"--upload_requests_file=cmd_file.txt"} |
| |
| if err := FlagsToConfig(fs, cfg, args, mockLookupEnv(env)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig := newDefaultConfigForTest(env) |
| expectedConfig.UploadRequestsFile = "env_file.txt" // Env should override command line |
| |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| |
| // Test environment variable overriding command-line argument for client-config flag (RS_rs_service). |
| env = map[string]string{ |
| "RS_rs_service": "env_rs_service", |
| } |
| fs = flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg = newDefaultConfigForTest(env) |
| registerRsproxyFlags(fs, cfg) |
| |
| args = []string{"--rs_service=cmd_rs_service"} |
| |
| if err := FlagsToConfig(fs, cfg, args, mockLookupEnv(env)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig = newDefaultConfigForTest(env) |
| expectedConfig.ClientConfig.ResultStoreService = "env_rs_service" // Env should override command line |
| |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestParseDefault(t *testing.T) { |
| fs := flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg := newDefaultConfigForTest(nil) |
| registerRsproxyFlags(fs, cfg) |
| |
| if err := FlagsToConfig(fs, cfg, []string{}, mockLookupEnv(nil)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig := newDefaultConfigForTest(nil) |
| |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestWrappedCommandWithReadyFileFails(t *testing.T) { |
| cfg := &mainConfig{ |
| ReadyFile: "some_file", |
| WrappedCommand: []string{"echo", "hello"}, |
| } |
| err := verifyFlags(cfg) |
| if err == nil { |
| t.Errorf("Expected an error when using --ready_file with a wrapped command, but got none") |
| } |
| } |
| |
| func TestParseSetRSWins(t *testing.T) { |
| env := map[string]string{ |
| "FLAG_upload_requests_file": "flag_file.txt", |
| "RS_upload_requests_file": "rs_file.txt", |
| } |
| fs := flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg := newDefaultConfigForTest(env) |
| registerRsproxyFlags(fs, cfg) |
| |
| if err := FlagsToConfig(fs, cfg, []string{}, mockLookupEnv(env)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig := newDefaultConfigForTest(env) |
| expectedConfig.UploadRequestsFile = "rs_file.txt" |
| |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| |
| // Test client-config flag |
| env = map[string]string{ |
| "FLAG_rs_service": "flag_rs_service", |
| "RS_rs_service": "rs_rs_service", |
| } |
| fs = flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg = newDefaultConfigForTest(env) |
| registerRsproxyFlags(fs, cfg) |
| |
| if err := FlagsToConfig(fs, cfg, []string{}, mockLookupEnv(env)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig = newDefaultConfigForTest(env) |
| expectedConfig.ClientConfig.ResultStoreService = "rs_rs_service" |
| |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestFlagsToConfig_CommandLineArgs(t *testing.T) { |
| fs := flag.NewFlagSet(t.Name(), flag.ContinueOnError) |
| cfg := newDefaultConfigForTest(nil) |
| registerRsproxyFlags(fs, cfg) |
| |
| args := []string{ |
| "--project_id=my-test-project", |
| "--quiet", |
| "--upload_requests_file=/tmp/reqs.pb", |
| } |
| |
| if err := FlagsToConfig(fs, cfg, args, mockLookupEnv(nil)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| expectedConfig := newDefaultConfigForTest(nil) |
| expectedConfig.UploadRequestsFile = "/tmp/reqs.pb" |
| expectedConfig.ProjectId = "my-test-project" |
| expectedConfig.QuietMode = true |
| |
| // cmp.Diff provides a detailed diff of the two structs. |
| if diff := cmp.Diff(expectedConfig, cfg); diff != "" { |
| t.Errorf("FlagsToConfig mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestTLSFlags(t *testing.T) { |
| tests := []struct { |
| name string |
| args []string |
| env map[string]string |
| verifyFn func(*testing.T, *mainConfig) |
| }{ |
| { |
| name: "tls_server_name_flag", |
| args: []string{"--tls_server_name=global.example.com"}, |
| verifyFn: func(t *testing.T, cfg *mainConfig) { |
| if cfg.TLSServerName != "global.example.com" { |
| t.Errorf("TLSServerName = %q, want %q", cfg.TLSServerName, "global.example.com") |
| } |
| }, |
| }, |
| { |
| name: "rs_tls_server_name_flag", |
| args: []string{"--rs_tls_server_name=rs.example.com"}, |
| verifyFn: func(t *testing.T, cfg *mainConfig) { |
| if cfg.ResultStoreTLSServerName != "rs.example.com" { |
| t.Errorf("ResultStoreTLSServerName = %q, want %q", cfg.ResultStoreTLSServerName, "rs.example.com") |
| } |
| }, |
| }, |
| { |
| name: "cas_tls_server_name_flag", |
| args: []string{"--cas_tls_server_name=cas.example.com"}, |
| verifyFn: func(t *testing.T, cfg *mainConfig) { |
| if cfg.CASTLSServerName != "cas.example.com" { |
| t.Errorf("CASTLSServerName = %q, want %q", cfg.CASTLSServerName, "cas.example.com") |
| } |
| }, |
| }, |
| { |
| name: "rs_tls_server_name_env", |
| env: map[string]string{"RS_rs_tls_server_name": "rs-env.example.com"}, |
| verifyFn: func(t *testing.T, cfg *mainConfig) { |
| if cfg.ResultStoreTLSServerName != "rs-env.example.com" { |
| t.Errorf("ResultStoreTLSServerName = %q, want %q", cfg.ResultStoreTLSServerName, "rs-env.example.com") |
| } |
| }, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| fs := flag.NewFlagSet(tt.name, flag.ContinueOnError) |
| cfg := newDefaultConfigForTest(tt.env) |
| registerRsproxyFlags(fs, cfg) |
| |
| if err := FlagsToConfig(fs, cfg, tt.args, mockLookupEnv(tt.env)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| tt.verifyFn(t, cfg) |
| }) |
| } |
| } |
| |
| func TestStringSliceDeduplication(t *testing.T) { |
| tests := []struct { |
| name string |
| args []string |
| expected []string |
| }{ |
| { |
| name: "no_duplicates", |
| args: []string{"--post_build_uploads=a,b", "--post_build_uploads=c"}, |
| expected: []string{"a", "b", "c"}, |
| }, |
| { |
| name: "with_duplicates_repeatable", |
| args: []string{"--post_build_uploads=a", "--post_build_uploads=a"}, |
| expected: []string{"a"}, |
| }, |
| { |
| name: "with_duplicates_comma_separated", |
| args: []string{"--post_build_uploads=a,a"}, |
| expected: []string{"a"}, |
| }, |
| { |
| name: "mixed_duplicates", |
| args: []string{"--post_build_uploads=a,b", "--post_build_uploads=b,c", "--post_build_uploads=a"}, |
| expected: []string{"a", "b", "c"}, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| fs := flag.NewFlagSet(tt.name, flag.ContinueOnError) |
| cfg := &mainConfig{} |
| registerRsproxyFlags(fs, cfg) |
| |
| if err := FlagsToConfig(fs, cfg, tt.args, mockLookupEnv(nil)); err != nil { |
| t.Fatalf("FlagsToConfig failed: %v", err) |
| } |
| |
| if diff := cmp.Diff(tt.expected, []string(cfg.PostBuildUploads)); diff != "" { |
| t.Errorf("PostBuildUploads deduplication mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |