-
Notifications
You must be signed in to change notification settings - Fork 15
[test] Add tests for tty.IsRunningInContainer #262
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
Changes from all commits
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,390 @@ | ||||||||||||||||||
| package tty | ||||||||||||||||||
|
|
||||||||||||||||||
| import ( | ||||||||||||||||||
| "os" | ||||||||||||||||||
| "testing" | ||||||||||||||||||
|
|
||||||||||||||||||
| "github.com/stretchr/testify/assert" | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestIsRunningInContainer_EnvironmentVariable(t *testing.T) { | ||||||||||||||||||
| tests := []struct { | ||||||||||||||||||
| name string | ||||||||||||||||||
| envValue string | ||||||||||||||||||
| // Note: expected value may vary based on actual environment | ||||||||||||||||||
| // If running in a container, file-based checks may return true | ||||||||||||||||||
| expectEnvDetection bool | ||||||||||||||||||
| }{ | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "RUNNING_IN_CONTAINER set to true", | ||||||||||||||||||
| envValue: "true", | ||||||||||||||||||
| expectEnvDetection: true, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "RUNNING_IN_CONTAINER set to false", | ||||||||||||||||||
| envValue: "false", | ||||||||||||||||||
| expectEnvDetection: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "RUNNING_IN_CONTAINER set to empty string", | ||||||||||||||||||
| envValue: "", | ||||||||||||||||||
| expectEnvDetection: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "RUNNING_IN_CONTAINER not set", | ||||||||||||||||||
| envValue: "__UNSET__", | ||||||||||||||||||
| expectEnvDetection: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "RUNNING_IN_CONTAINER set to invalid value", | ||||||||||||||||||
| envValue: "yes", | ||||||||||||||||||
| expectEnvDetection: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| for _, tt := range tests { | ||||||||||||||||||
| t.Run(tt.name, func(t *testing.T) { | ||||||||||||||||||
| // Save original environment variable | ||||||||||||||||||
| originalValue, originalExists := os.LookupEnv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| defer func() { | ||||||||||||||||||
| if originalExists { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", originalValue) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } | ||||||||||||||||||
| }() | ||||||||||||||||||
|
|
||||||||||||||||||
| // Set test environment variable | ||||||||||||||||||
| if tt.envValue == "__UNSET__" { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", tt.envValue) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| result := IsRunningInContainer() | ||||||||||||||||||
|
|
||||||||||||||||||
| // If the environment variable detection should trigger, verify it does | ||||||||||||||||||
| // If not, the result depends on file-based checks which we can't control | ||||||||||||||||||
| if tt.expectEnvDetection { | ||||||||||||||||||
| assert.True(t, result, "Expected true when RUNNING_IN_CONTAINER=%s", tt.envValue) | ||||||||||||||||||
| } | ||||||||||||||||||
| // Note: We don't assert false here because file-based checks might return true | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestIsRunningInContainer_FileBasedDetection(t *testing.T) { | ||||||||||||||||||
| // Clear the environment variable to test only file-based detection | ||||||||||||||||||
| originalValue, originalExists := os.LookupEnv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| defer func() { | ||||||||||||||||||
| if originalExists { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", originalValue) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } | ||||||||||||||||||
| }() | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
|
|
||||||||||||||||||
| result := IsRunningInContainer() | ||||||||||||||||||
|
|
||||||||||||||||||
| // Check for /.dockerenv file | ||||||||||||||||||
| _, dockerEnvErr := os.Stat("/.dockerenv") | ||||||||||||||||||
| dockerEnvExists := dockerEnvErr == nil | ||||||||||||||||||
|
|
||||||||||||||||||
| // Check for container indicators in /proc/1/cgroup | ||||||||||||||||||
| cgroupData, cgroupErr := os.ReadFile("/proc/1/cgroup") | ||||||||||||||||||
| cgroupIndicatesContainer := false | ||||||||||||||||||
| if cgroupErr == nil { | ||||||||||||||||||
| content := string(cgroupData) | ||||||||||||||||||
| cgroupIndicatesContainer = containsAny(content, []string{"docker", "containerd", "kubepods", "lxc"}) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| expectedResult := dockerEnvExists || cgroupIndicatesContainer | ||||||||||||||||||
|
|
||||||||||||||||||
| // Document the actual file system state for debugging | ||||||||||||||||||
| t.Logf("/.dockerenv exists: %v", dockerEnvExists) | ||||||||||||||||||
| t.Logf("/proc/1/cgroup indicates container: %v", cgroupIndicatesContainer) | ||||||||||||||||||
| t.Logf("IsRunningInContainer result: %v", result) | ||||||||||||||||||
| t.Logf("Expected result: %v", expectedResult) | ||||||||||||||||||
|
|
||||||||||||||||||
| assert.Equal(t, expectedResult, result, | ||||||||||||||||||
| "IsRunningInContainer should match file-based detection (dockerenv: %v, cgroup: %v)", | ||||||||||||||||||
| dockerEnvExists, cgroupIndicatesContainer) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestIsRunningInContainer_AllMethodsCombined(t *testing.T) { | ||||||||||||||||||
| // This test documents the complete detection logic | ||||||||||||||||||
| // It will pass regardless of environment, serving as documentation | ||||||||||||||||||
|
|
||||||||||||||||||
| // Save original environment | ||||||||||||||||||
| originalValue, originalExists := os.LookupEnv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| defer func() { | ||||||||||||||||||
| if originalExists { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", originalValue) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } | ||||||||||||||||||
| }() | ||||||||||||||||||
|
|
||||||||||||||||||
| tests := []struct { | ||||||||||||||||||
| name string | ||||||||||||||||||
| setupEnv func() | ||||||||||||||||||
| verifyAgainstFilesSys bool | ||||||||||||||||||
| }{ | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "with RUNNING_IN_CONTAINER=true", | ||||||||||||||||||
| setupEnv: func() { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", "true") | ||||||||||||||||||
| }, | ||||||||||||||||||
| verifyAgainstFilesSys: false, // Env var takes precedence | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "without RUNNING_IN_CONTAINER env var", | ||||||||||||||||||
| setupEnv: func() { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| }, | ||||||||||||||||||
| verifyAgainstFilesSys: true, // Should check files | ||||||||||||||||||
| }, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| for _, tt := range tests { | ||||||||||||||||||
| t.Run(tt.name, func(t *testing.T) { | ||||||||||||||||||
| tt.setupEnv() | ||||||||||||||||||
|
|
||||||||||||||||||
| result := IsRunningInContainer() | ||||||||||||||||||
|
|
||||||||||||||||||
| // Log the detection state | ||||||||||||||||||
| _, dockerEnvErr := os.Stat("/.dockerenv") | ||||||||||||||||||
| dockerEnvExists := dockerEnvErr == nil | ||||||||||||||||||
|
|
||||||||||||||||||
| cgroupData, cgroupErr := os.ReadFile("/proc/1/cgroup") | ||||||||||||||||||
| cgroupIndicatesContainer := false | ||||||||||||||||||
| if cgroupErr == nil { | ||||||||||||||||||
| content := string(cgroupData) | ||||||||||||||||||
| cgroupIndicatesContainer = containsAny(content, []string{"docker", "containerd", "kubepods", "lxc"}) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| envVarSet := os.Getenv("RUNNING_IN_CONTAINER") == "true" | ||||||||||||||||||
|
|
||||||||||||||||||
| t.Logf("Detection methods:") | ||||||||||||||||||
| t.Logf(" - RUNNING_IN_CONTAINER=true: %v", envVarSet) | ||||||||||||||||||
| t.Logf(" - /.dockerenv exists: %v", dockerEnvExists) | ||||||||||||||||||
| t.Logf(" - /proc/1/cgroup indicates container: %v", cgroupIndicatesContainer) | ||||||||||||||||||
| t.Logf(" - Final result: %v", result) | ||||||||||||||||||
|
|
||||||||||||||||||
| // Verify the result matches at least one detection method | ||||||||||||||||||
| expectedByAnyMethod := envVarSet || dockerEnvExists || cgroupIndicatesContainer | ||||||||||||||||||
|
|
||||||||||||||||||
| if tt.verifyAgainstFilesSys { | ||||||||||||||||||
| // When env var is not set, result should match file-based checks | ||||||||||||||||||
| expected := dockerEnvExists || cgroupIndicatesContainer | ||||||||||||||||||
| assert.Equal(t, expected, result, "Should match file-based detection") | ||||||||||||||||||
| } else { | ||||||||||||||||||
| // When env var is set to "true", should always return true | ||||||||||||||||||
| assert.True(t, result, "Should return true when RUNNING_IN_CONTAINER=true") | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // General assertion: result should be consistent with detection methods | ||||||||||||||||||
| assert.Equal(t, expectedByAnyMethod, result, | ||||||||||||||||||
| "Result should match at least one detection method") | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestIsRunningInContainer_EdgeCases(t *testing.T) { | ||||||||||||||||||
| // Save original environment | ||||||||||||||||||
| originalValue, originalExists := os.LookupEnv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| defer func() { | ||||||||||||||||||
| if originalExists { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", originalValue) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } | ||||||||||||||||||
| }() | ||||||||||||||||||
|
|
||||||||||||||||||
| tests := []struct { | ||||||||||||||||||
| name string | ||||||||||||||||||
| envValue string | ||||||||||||||||||
| wantTrue bool | ||||||||||||||||||
| }{ | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "case sensitive check - 'True' should not match", | ||||||||||||||||||
| envValue: "True", | ||||||||||||||||||
| wantTrue: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "case sensitive check - 'TRUE' should not match", | ||||||||||||||||||
| envValue: "TRUE", | ||||||||||||||||||
| wantTrue: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "whitespace in value", | ||||||||||||||||||
| envValue: " true ", | ||||||||||||||||||
| wantTrue: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "one value", | ||||||||||||||||||
| envValue: "1", | ||||||||||||||||||
| wantTrue: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "yes value", | ||||||||||||||||||
| envValue: "yes", | ||||||||||||||||||
| wantTrue: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| for _, tt := range tests { | ||||||||||||||||||
| t.Run(tt.name, func(t *testing.T) { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", tt.envValue) | ||||||||||||||||||
|
|
||||||||||||||||||
| result := IsRunningInContainer() | ||||||||||||||||||
|
|
||||||||||||||||||
| // The environment variable check is strict: only "true" (lowercase) works | ||||||||||||||||||
| // So if wantTrue is false and we're testing env var logic, it should respect that | ||||||||||||||||||
| // However, file-based checks might still return true | ||||||||||||||||||
| if tt.wantTrue { | ||||||||||||||||||
| assert.True(t, result, "Expected true for env value: %s", tt.envValue) | ||||||||||||||||||
| } | ||||||||||||||||||
| // Note: Not asserting false because file-based detection might return true | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestIsRunningInContainer_Consistency(t *testing.T) { | ||||||||||||||||||
| // Test that multiple calls return the same result (no race conditions) | ||||||||||||||||||
| // Save original environment | ||||||||||||||||||
| originalValue, originalExists := os.LookupEnv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| defer func() { | ||||||||||||||||||
| if originalExists { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", originalValue) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } | ||||||||||||||||||
| }() | ||||||||||||||||||
|
|
||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
|
|
||||||||||||||||||
| // Call multiple times and verify consistency | ||||||||||||||||||
| results := make([]bool, 10) | ||||||||||||||||||
| for i := 0; i < 10; i++ { | ||||||||||||||||||
| results[i] = IsRunningInContainer() | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // All results should be identical | ||||||||||||||||||
| firstResult := results[0] | ||||||||||||||||||
| for i, result := range results { | ||||||||||||||||||
| assert.Equal(t, firstResult, result, "Call %d should return same result as first call", i) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestIsRunningInContainer_ConcurrentAccess(t *testing.T) { | ||||||||||||||||||
| // Test thread safety with concurrent calls | ||||||||||||||||||
| // Save original environment | ||||||||||||||||||
| originalValue, originalExists := os.LookupEnv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| defer func() { | ||||||||||||||||||
| if originalExists { | ||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", originalValue) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| os.Unsetenv("RUNNING_IN_CONTAINER") | ||||||||||||||||||
| } | ||||||||||||||||||
| }() | ||||||||||||||||||
|
|
||||||||||||||||||
| os.Setenv("RUNNING_IN_CONTAINER", "true") | ||||||||||||||||||
|
|
||||||||||||||||||
| // Run 100 concurrent checks | ||||||||||||||||||
| done := make(chan bool, 100) | ||||||||||||||||||
| for i := 0; i < 100; i++ { | ||||||||||||||||||
| go func() { | ||||||||||||||||||
| result := IsRunningInContainer() | ||||||||||||||||||
| // When env var is "true", should always return true | ||||||||||||||||||
| assert.True(t, result, "Concurrent call should return true") | ||||||||||||||||||
| done <- true | ||||||||||||||||||
| }() | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Wait for all goroutines | ||||||||||||||||||
| for i := 0; i < 100; i++ { | ||||||||||||||||||
| <-done | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Helper function to check if a string contains any of the given substrings | ||||||||||||||||||
| func containsAny(s string, substrings []string) bool { | ||||||||||||||||||
| for _, substr := range substrings { | ||||||||||||||||||
| if len(s) >= len(substr) { | ||||||||||||||||||
| for i := 0; i <= len(s)-len(substr); i++ { | ||||||||||||||||||
| if s[i:i+len(substr)] == substr { | ||||||||||||||||||
| return true | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return false | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+313
to
+324
|
||||||||||||||||||
|
|
||||||||||||||||||
| func TestContainsAny_Helper(t *testing.T) { | ||||||||||||||||||
| // Test the helper function itself | ||||||||||||||||||
| tests := []struct { | ||||||||||||||||||
| name string | ||||||||||||||||||
| input string | ||||||||||||||||||
| substrings []string | ||||||||||||||||||
| want bool | ||||||||||||||||||
| }{ | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "contains docker", | ||||||||||||||||||
| input: "0::/docker/abc123", | ||||||||||||||||||
| substrings: []string{"docker", "containerd"}, | ||||||||||||||||||
| want: true, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "contains containerd", | ||||||||||||||||||
| input: "0::/system.slice/containerd.service", | ||||||||||||||||||
| substrings: []string{"docker", "containerd"}, | ||||||||||||||||||
| want: true, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "contains kubepods", | ||||||||||||||||||
| input: "0::/kubepods/besteffort/pod123", | ||||||||||||||||||
| substrings: []string{"kubepods", "lxc"}, | ||||||||||||||||||
| want: true, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "contains lxc", | ||||||||||||||||||
| input: "0::/lxc/container", | ||||||||||||||||||
| substrings: []string{"docker", "lxc"}, | ||||||||||||||||||
| want: true, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "does not contain any", | ||||||||||||||||||
| input: "0::/user.slice/user-1000.slice", | ||||||||||||||||||
| substrings: []string{"docker", "containerd", "kubepods", "lxc"}, | ||||||||||||||||||
| want: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "empty string", | ||||||||||||||||||
| input: "", | ||||||||||||||||||
| substrings: []string{"docker"}, | ||||||||||||||||||
| want: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "empty substrings", | ||||||||||||||||||
| input: "some text", | ||||||||||||||||||
| substrings: []string{}, | ||||||||||||||||||
| want: false, | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| name: "partial match should not trigger", | ||||||||||||||||||
| input: "dockerized", | ||||||||||||||||||
| substrings: []string{"docker"}, | ||||||||||||||||||
| want: true, // Note: Our simple implementation will match this | ||||||||||||||||||
|
Comment on lines
+377
to
+380
|
||||||||||||||||||
| name: "partial match should not trigger", | |
| input: "dockerized", | |
| substrings: []string{"docker"}, | |
| want: true, // Note: Our simple implementation will match this | |
| name: "partial substring match is allowed", | |
| input: "dockerized", | |
| substrings: []string{"docker"}, | |
| want: true, // Substring matching is intentional: "docker" in "dockerized" should count as a match |
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.
Using
assert.True(t, ...)directly inside a goroutine is unsafe because thetesting.Tobject is not goroutine-safe and can cause data races. Instead, capture the result in the goroutine and perform assertions in the main goroutine after collecting all results. Similar pattern exists inTestRequireSession_DifcDisabled_Concurrentwhere results are collected via channels first.