forked from irfn/goconfig
-
Notifications
You must be signed in to change notification settings - Fork 2
[PWI-1849][Henrico] feat!: add thread-safe config implementation and upgrade to gomod #1
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
b8768a0
feat: add thread-safe config implementation and upgrade to gomod
ricoferdian-gtf fdef4c9
chore: add github workflows
ricoferdian-gtf a31a2d3
feat: update goconfig to v2
ricoferdian-gtf 4d41c53
feat: remove glide and utilize go 1.18 as primary
ricoferdian-gtf 1f25a68
chore: retrigger go mod tidy to cleanup and use correct dep version
ricoferdian-gtf 1fa4957
chore: remove unwanted scripts
ricoferdian-gtf 8ecb9b8
refactor: set config and mutex as member of BaseConfig struct
ricoferdian-gtf 1235aaf
refactor: add separate interface for interacting with base config
ricoferdian-gtf 7fbc732
chore: adjust unittests
ricoferdian-gtf e2f5d14
chore: rename base receiver for base config
ricoferdian-gtf 48b5d1c
feat: add goroutine lock double-checking mechanism
ricoferdian-gtf ced0bb8
chore: change module name and separate test package
ricoferdian-gtf 875a251
chore: rename interface
ricoferdian-gtf 140aff2
feat: remove map with mutex approach and utilize sync.Map instead
ricoferdian-gtf 236868f
chore: remove exercise on base config load test scenario as viper doe…
ricoferdian-gtf ea2cd4f
feat: load config once when using sync map
ricoferdian-gtf 0db810d
chore: add comments in API interfaces
ricoferdian-gtf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| name: Go Tests | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ 'master' ] | ||
| pull_request: | ||
| branches: [ '*' ] | ||
|
|
||
| jobs: | ||
| test: | ||
| name: Run Unit Tests | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v3 | ||
|
|
||
| - name: Set up Go 1.18 | ||
| uses: actions/setup-go@v3 | ||
| with: | ||
| go-version: 1.18 | ||
| cache: true | ||
|
|
||
| - name: Install dependencies | ||
| run: go mod download | ||
|
|
||
| - name: Run tests | ||
| run: | | ||
| find . -type f -name "*_test.go" -not -path "./vendor/*" | | ||
| xargs dirname | | ||
| sort -u | | ||
| xargs -I{} sh -c 'echo "Testing {}"; cd {} && go test -v ./...' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| vendor/ | ||
| .idea |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,297 @@ | ||
| package goconfig_test | ||
|
|
||
| import ( | ||
| "github.com/gojekfarm/goconfig/v2" | ||
| "runtime" | ||
| "sync" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| func TestConcurrentGetValue(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 100 | ||
| const numOperations = 1000 | ||
| var wg sync.WaitGroup | ||
|
|
||
| // Test concurrent access to the same key | ||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| for j := 0; j < numOperations; j++ { | ||
| value := baseConfig.GetValue("foo") | ||
| if value != "bar" { | ||
| t.Errorf("Expected 'bar', got '%s'", value) | ||
| } | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentGetValueDifferentKeys(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 50 | ||
| var wg sync.WaitGroup | ||
|
|
||
| // Test concurrent access to different keys | ||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func(goroutineID int) { | ||
| defer wg.Done() | ||
| if goroutineID%2 == 0 { | ||
| value := baseConfig.GetValue("foo") | ||
| if value != "bar" { | ||
| t.Errorf("Expected 'bar', got '%s'", value) | ||
| } | ||
| } else { | ||
| value := baseConfig.GetValue("new_relic_app_name") | ||
| if value != "foo" { | ||
| t.Errorf("Expected 'foo', got '%s'", value) | ||
| } | ||
| } | ||
| }(i) | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentGetIntValue(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 100 | ||
| const numOperations = 500 | ||
| var wg sync.WaitGroup | ||
|
|
||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| for j := 0; j < numOperations; j++ { | ||
| value := baseConfig.GetIntValue("someInt") | ||
| if value != 1 { | ||
| t.Errorf("Expected 1, got %d", value) | ||
| } | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentGetOptionalValue(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 50 | ||
| var wg sync.WaitGroup | ||
|
|
||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| // Test existing key | ||
| value := baseConfig.GetOptionalValue("foo", "default") | ||
| if value != "bar" { | ||
| t.Errorf("Expected 'bar', got '%s'", value) | ||
| } | ||
|
|
||
| // Test non-existing key with default | ||
| value = baseConfig.GetOptionalValue("nonexistent", "default") | ||
| if value != "default" { | ||
| t.Errorf("Expected 'default', got '%s'", value) | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentGetOptionalIntValue(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 50 | ||
| var wg sync.WaitGroup | ||
|
|
||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| // Test existing key | ||
| value := baseConfig.GetOptionalIntValue("someInt", 999) | ||
| if value != 1 { | ||
| t.Errorf("Expected 1, got %d", value) | ||
| } | ||
|
|
||
| // Test non-existing key with default | ||
| value = baseConfig.GetOptionalIntValue("nonexistentInt", 999) | ||
| if value != 999 { | ||
| t.Errorf("Expected 999, got %d", value) | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentGetFeature(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 50 | ||
| var wg sync.WaitGroup | ||
|
|
||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| // Test feature that is true | ||
| value := baseConfig.GetFeature("someFeature") | ||
| if !value { | ||
| t.Errorf("Expected true, got %v", value) | ||
| } | ||
|
|
||
| // Test feature that is false | ||
| value = baseConfig.GetFeature("someOtherFeature") | ||
| if value { | ||
| t.Errorf("Expected false, got %v", value) | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentMixedOperations(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 20 | ||
| const numOperations = 100 | ||
| var wg sync.WaitGroup | ||
|
|
||
| // Test mixed concurrent operations | ||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func(goroutineID int) { | ||
| defer wg.Done() | ||
| for j := 0; j < numOperations; j++ { | ||
| switch goroutineID % 5 { | ||
| case 0: | ||
| baseConfig.GetValue("foo") | ||
| case 1: | ||
| baseConfig.GetIntValue("someInt") | ||
| case 2: | ||
| baseConfig.GetOptionalValue("foo", "default") | ||
| case 3: | ||
| baseConfig.GetOptionalIntValue("someInt", 999) | ||
| case 4: | ||
| baseConfig.GetFeature("someFeature") | ||
| } | ||
| } | ||
| }(i) | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestNoDeadlockScenario(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 100 | ||
| var wg sync.WaitGroup | ||
| timeout := time.After(10 * time.Second) | ||
| done := make(chan bool) | ||
|
|
||
| // Start goroutines | ||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| // Rapidly access different methods that use the same mutex | ||
| for j := 0; j < 100; j++ { | ||
| baseConfig.GetValue("foo") | ||
| baseConfig.GetIntValue("someInt") | ||
| baseConfig.GetOptionalValue("nonexistent", "default") | ||
| baseConfig.GetOptionalIntValue("nonexistentInt", 999) | ||
| baseConfig.GetFeature("someFeature") | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| // Wait for completion or timeout | ||
| go func() { | ||
| wg.Wait() | ||
| done <- true | ||
| }() | ||
|
|
||
| select { | ||
| case <-done: | ||
| // Test completed successfully, no deadlock | ||
| case <-timeout: | ||
| t.Fatal("Test timed out - possible deadlock detected") | ||
| } | ||
| } | ||
|
|
||
| func TestRaceConditionDetection(t *testing.T) { | ||
| baseConfig := goconfig.NewBaseConfig() | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 50 | ||
| var wg sync.WaitGroup | ||
|
|
||
| // Create high contention scenario | ||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func() { | ||
| defer wg.Done() | ||
| for j := 0; j < 1000; j++ { | ||
| // All goroutines access the same key to create contention | ||
| baseConfig.GetValue("foo") | ||
| runtime.Gosched() // Yield to increase chance of race conditions | ||
| } | ||
| }() | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
|
|
||
| func TestConcurrentCacheEviction(t *testing.T) { | ||
| // Test scenario where cache might be cleared while being accessed | ||
| baseConfig := goconfig.BaseConfig{} | ||
| baseConfig.Load() | ||
|
|
||
| const numGoroutines = 20 | ||
| var wg sync.WaitGroup | ||
|
|
||
| for i := 0; i < numGoroutines; i++ { | ||
| wg.Add(1) | ||
| go func(goroutineID int) { | ||
| defer wg.Done() | ||
| if goroutineID == 0 { | ||
| // One goroutine periodically clears cache | ||
| for j := 0; j < 10; j++ { | ||
| time.Sleep(50 * time.Millisecond) | ||
| baseConfig.Load() | ||
| } | ||
| } else { | ||
| // Other goroutines continuously access values | ||
| for j := 0; j < 100; j++ { | ||
| baseConfig.GetValue("foo") | ||
| time.Sleep(10 * time.Millisecond) | ||
| } | ||
| } | ||
| }(i) | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.