feat: P0-2b implement ControlHost via xcecli_hostctrl + fix allFlag race
- backend/clib.go: Replace ControlHost stub with real implementation using xcecli_hostctrl, following the same pattern as ControlQueue - controllers/hosts.go: Remove package-level allFlag variable; preCheckHostControlRequest now returns (bool, error) so the allFlag is a local value in the caller, eliminating the data race - controllers/queues.go: Same fix for preCheckQueueControlRequest - Add unit tests for both preCheck functions verifying return values, error cases, and concurrent safety - Add compile-time verification test for ControlHost stub signature
This commit is contained in:
parent
ff940223ec
commit
8124cda83e
6 changed files with 330 additions and 26 deletions
139
src/frontend/apiserver/controllers/queue_control_test.go
Normal file
139
src/frontend/apiserver/controllers/queue_control_test.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"apiserver/models"
|
||||
"apiserver/store"
|
||||
"apiserver/utils"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func resetQueueControlState() {
|
||||
store.InitStores()
|
||||
utils.Log = zap.NewNop()
|
||||
}
|
||||
|
||||
// TestPreCheckQueueControlRequest_AllFlag verifies that when queue_name is
|
||||
// ["all"], the returned bool is true.
|
||||
func TestPreCheckQueueControlRequest_AllFlag(t *testing.T) {
|
||||
resetQueueControlState()
|
||||
|
||||
req := &models.QueueControlRequest{
|
||||
ControlAction: "close",
|
||||
QueueName: []string{"all"},
|
||||
}
|
||||
|
||||
allFlag, err := preCheckQueueControlRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !allFlag {
|
||||
t.Error("expected allFlag=true when queue_name is [\"all\"]")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreCheckQueueControlRequest_NotAll verifies that when queue_name is a
|
||||
// specific queue, the returned bool is false.
|
||||
func TestPreCheckQueueControlRequest_NotAll(t *testing.T) {
|
||||
resetQueueControlState()
|
||||
|
||||
req := &models.QueueControlRequest{
|
||||
ControlAction: "open",
|
||||
QueueName: []string{"normal"},
|
||||
}
|
||||
|
||||
allFlag, err := preCheckQueueControlRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if allFlag {
|
||||
t.Error("expected allFlag=false when queue_name is [\"normal\"]")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreCheckQueueControlRequest_NoRace verifies that concurrent calls to
|
||||
// preCheckQueueControlRequest do not share state via a package-level variable.
|
||||
func TestPreCheckQueueControlRequest_NoRace(t *testing.T) {
|
||||
t.Parallel()
|
||||
resetQueueControlState()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
|
||||
results := make([]bool, goroutines)
|
||||
errors := make([]error, goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
var req models.QueueControlRequest
|
||||
if idx%2 == 0 {
|
||||
req = models.QueueControlRequest{
|
||||
ControlAction: "close",
|
||||
QueueName: []string{"all"},
|
||||
}
|
||||
} else {
|
||||
req = models.QueueControlRequest{
|
||||
ControlAction: "open",
|
||||
QueueName: []string{"normal"},
|
||||
}
|
||||
}
|
||||
results[idx], errors[idx] = preCheckQueueControlRequest(&req)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
if errors[i] != nil {
|
||||
t.Errorf("goroutine %d: unexpected error: %v", i, errors[i])
|
||||
continue
|
||||
}
|
||||
if i%2 == 0 && !results[i] {
|
||||
t.Errorf("goroutine %d: expected allFlag=true for [\"all\"]", i)
|
||||
}
|
||||
if i%2 != 0 && results[i] {
|
||||
t.Errorf("goroutine %d: expected allFlag=false for [\"normal\"]", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreCheckQueueControlRequest_InvalidAction verifies that an unsupported
|
||||
// action returns an error.
|
||||
func TestPreCheckQueueControlRequest_InvalidAction(t *testing.T) {
|
||||
resetQueueControlState()
|
||||
|
||||
req := &models.QueueControlRequest{
|
||||
ControlAction: "invalid_action",
|
||||
QueueName: []string{"normal"},
|
||||
}
|
||||
|
||||
_, err := preCheckQueueControlRequest(req)
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid action, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreCheckQueueControlRequest_CommentTooLong verifies that a comment
|
||||
// exceeding 512 characters returns an error.
|
||||
func TestPreCheckQueueControlRequest_CommentTooLong(t *testing.T) {
|
||||
resetQueueControlState()
|
||||
|
||||
longComment := make([]byte, 513)
|
||||
for i := range longComment {
|
||||
longComment[i] = 'a'
|
||||
}
|
||||
|
||||
req := &models.QueueControlRequest{
|
||||
ControlAction: "close",
|
||||
QueueName: []string{"normal"},
|
||||
Comment: string(longComment),
|
||||
}
|
||||
|
||||
_, err := preCheckQueueControlRequest(req)
|
||||
if err == nil {
|
||||
t.Error("expected error for comment exceeding 512 chars, got nil")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue