test(service): add tests for task defaults and job status
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -829,6 +829,356 @@ func TestGetJob_FallbackToHistory_HistoryError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// New scheduling field mapping tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestSubmitJob_AllSchedulingFields(t *testing.T) {
|
||||
jobID := int32(999)
|
||||
|
||||
// Prepare all 22 new scheduling field values
|
||||
var (
|
||||
memoryPerNode = int64(4096)
|
||||
memoryPerCpu = int64(1024)
|
||||
nodes = "2"
|
||||
tasks = int32(4)
|
||||
cpusPerTask = int32(2)
|
||||
constraints = "gpu&fast"
|
||||
reservation = "resv01"
|
||||
account = "proj-alpha"
|
||||
nice = int32(100)
|
||||
mailType = "BEGIN,END,FAIL"
|
||||
mailUser = "admin@example.com"
|
||||
stdOut = "/tmp/job_%j.out"
|
||||
stdErr = "/tmp/job_%j.err"
|
||||
stdIn = "/dev/null"
|
||||
reqNodes = "node01,node02"
|
||||
exclNodes = "node03,node04"
|
||||
beginTime = int64(1700000000)
|
||||
deadline = int64(1700099999)
|
||||
array = "1-10"
|
||||
dependency = "afterok:123"
|
||||
requeue = true
|
||||
killOnNodeFail = true
|
||||
)
|
||||
|
||||
client, cleanup := mockJobServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var body slurm.JobSubmitReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
if body.Job == nil {
|
||||
t.Fatal("job desc is nil")
|
||||
}
|
||||
j := body.Job
|
||||
|
||||
// --- Existing fields still work ---
|
||||
if j.Script == nil || *j.Script != "#!/bin/bash\necho test" {
|
||||
t.Errorf("Script mismatch: %v", j.Script)
|
||||
}
|
||||
if j.Partition == nil || *j.Partition != "normal" {
|
||||
t.Errorf("Partition mismatch: %v", j.Partition)
|
||||
}
|
||||
if j.Qos == nil || *j.Qos != "high" {
|
||||
t.Errorf("QOS mismatch: %v", j.Qos)
|
||||
}
|
||||
if j.Name == nil || *j.Name != "full-test" {
|
||||
t.Errorf("Name mismatch: %v", j.Name)
|
||||
}
|
||||
if j.MinimumCpus == nil || *j.MinimumCpus != int32(8) {
|
||||
t.Errorf("MinimumCpus mismatch: %v", j.MinimumCpus)
|
||||
}
|
||||
|
||||
// --- 22 new scheduling fields ---
|
||||
|
||||
// MemoryPerNode → *Uint64NoVal
|
||||
if j.MemoryPerNode == nil || j.MemoryPerNode.Number == nil || *j.MemoryPerNode.Number != memoryPerNode {
|
||||
t.Errorf("MemoryPerNode mismatch: %v", j.MemoryPerNode)
|
||||
}
|
||||
// MemoryPerCpu → *Uint64NoVal
|
||||
if j.MemoryPerCpu == nil || j.MemoryPerCpu.Number == nil || *j.MemoryPerCpu.Number != memoryPerCpu {
|
||||
t.Errorf("MemoryPerCpu mismatch: %v", j.MemoryPerCpu)
|
||||
}
|
||||
// Nodes → *string
|
||||
if j.Nodes == nil || *j.Nodes != nodes {
|
||||
t.Errorf("Nodes mismatch: %v", j.Nodes)
|
||||
}
|
||||
// Tasks → *int32
|
||||
if j.Tasks == nil || *j.Tasks != tasks {
|
||||
t.Errorf("Tasks mismatch: got %v, want %d", j.Tasks, tasks)
|
||||
}
|
||||
// CpusPerTask → *int32
|
||||
if j.CpusPerTask == nil || *j.CpusPerTask != cpusPerTask {
|
||||
t.Errorf("CpusPerTask mismatch: got %v, want %d", j.CpusPerTask, cpusPerTask)
|
||||
}
|
||||
// Constraints → *string
|
||||
if j.Constraints == nil || *j.Constraints != constraints {
|
||||
t.Errorf("Constraints mismatch: %v", j.Constraints)
|
||||
}
|
||||
// Reservation → *string
|
||||
if j.Reservation == nil || *j.Reservation != reservation {
|
||||
t.Errorf("Reservation mismatch: %v", j.Reservation)
|
||||
}
|
||||
// Account → *string
|
||||
if j.Account == nil || *j.Account != account {
|
||||
t.Errorf("Account mismatch: %v", j.Account)
|
||||
}
|
||||
// Nice → *int32
|
||||
if j.Nice == nil || *j.Nice != nice {
|
||||
t.Errorf("Nice mismatch: got %v, want %d", j.Nice, nice)
|
||||
}
|
||||
// MailType → []string (comma-split)
|
||||
if len(j.MailType) != 3 || j.MailType[0] != "BEGIN" || j.MailType[1] != "END" || j.MailType[2] != "FAIL" {
|
||||
t.Errorf("MailType mismatch: %v", j.MailType)
|
||||
}
|
||||
// MailUser → *string
|
||||
if j.MailUser == nil || *j.MailUser != mailUser {
|
||||
t.Errorf("MailUser mismatch: %v", j.MailUser)
|
||||
}
|
||||
// StandardOutput → *string
|
||||
if j.StandardOutput == nil || *j.StandardOutput != stdOut {
|
||||
t.Errorf("StandardOutput mismatch: %v", j.StandardOutput)
|
||||
}
|
||||
// StandardError → *string
|
||||
if j.StandardError == nil || *j.StandardError != stdErr {
|
||||
t.Errorf("StandardError mismatch: %v", j.StandardError)
|
||||
}
|
||||
// StandardInput → *string
|
||||
if j.StandardInput == nil || *j.StandardInput != stdIn {
|
||||
t.Errorf("StandardInput mismatch: %v", j.StandardInput)
|
||||
}
|
||||
// RequiredNodes → CSVString ([]string)
|
||||
if len(j.RequiredNodes) != 2 || j.RequiredNodes[0] != "node01" || j.RequiredNodes[1] != "node02" {
|
||||
t.Errorf("RequiredNodes mismatch: %v", j.RequiredNodes)
|
||||
}
|
||||
// ExcludedNodes → CSVString ([]string)
|
||||
if len(j.ExcludedNodes) != 2 || j.ExcludedNodes[0] != "node03" || j.ExcludedNodes[1] != "node04" {
|
||||
t.Errorf("ExcludedNodes mismatch: %v", j.ExcludedNodes)
|
||||
}
|
||||
// BeginTime → *Uint64NoVal
|
||||
if j.BeginTime == nil || j.BeginTime.Number == nil || *j.BeginTime.Number != beginTime {
|
||||
t.Errorf("BeginTime mismatch: %v", j.BeginTime)
|
||||
}
|
||||
// Deadline → *int64 (NO wrapper)
|
||||
if j.Deadline == nil || *j.Deadline != deadline {
|
||||
t.Errorf("Deadline mismatch: %v", j.Deadline)
|
||||
}
|
||||
// Array → *string
|
||||
if j.Array == nil || *j.Array != array {
|
||||
t.Errorf("Array mismatch: %v", j.Array)
|
||||
}
|
||||
// Dependency → *string
|
||||
if j.Dependency == nil || *j.Dependency != dependency {
|
||||
t.Errorf("Dependency mismatch: %v", j.Dependency)
|
||||
}
|
||||
// Requeue → *bool
|
||||
if j.Requeue == nil || *j.Requeue != requeue {
|
||||
t.Errorf("Requeue mismatch: %v", j.Requeue)
|
||||
}
|
||||
// KillOnNodeFail → *bool
|
||||
if j.KillOnNodeFail == nil || *j.KillOnNodeFail != killOnNodeFail {
|
||||
t.Errorf("KillOnNodeFail mismatch: %v", j.KillOnNodeFail)
|
||||
}
|
||||
|
||||
resp := slurm.OpenapiJobSubmitResponse{
|
||||
Result: &slurm.JobSubmitResponseMsg{JobID: &jobID},
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}))
|
||||
defer cleanup()
|
||||
|
||||
svc := NewJobService(client, zap.NewNop())
|
||||
resp, err := svc.SubmitJob(context.Background(), &model.SubmitJobRequest{
|
||||
Script: "#!/bin/bash\necho test",
|
||||
Partition: "normal",
|
||||
QOS: "high",
|
||||
JobName: "full-test",
|
||||
CPUs: 8,
|
||||
TimeLimit: "60",
|
||||
MemoryPerNode: &memoryPerNode,
|
||||
MemoryPerCpu: &memoryPerCpu,
|
||||
Nodes: &nodes,
|
||||
Tasks: &tasks,
|
||||
CpusPerTask: &cpusPerTask,
|
||||
Constraints: &constraints,
|
||||
Reservation: &reservation,
|
||||
Account: &account,
|
||||
Nice: &nice,
|
||||
MailType: &mailType,
|
||||
MailUser: &mailUser,
|
||||
StandardOutput: &stdOut,
|
||||
StandardError: &stdErr,
|
||||
StandardInput: &stdIn,
|
||||
RequiredNodes: &reqNodes,
|
||||
ExcludedNodes: &exclNodes,
|
||||
BeginTime: &beginTime,
|
||||
Deadline: &deadline,
|
||||
Array: &array,
|
||||
Dependency: &dependency,
|
||||
Requeue: &requeue,
|
||||
KillOnNodeFail: &killOnNodeFail,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SubmitJob: %v", err)
|
||||
}
|
||||
if resp.JobID != 999 {
|
||||
t.Errorf("expected JobID 999, got %d", resp.JobID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitJob_BackwardCompat(t *testing.T) {
|
||||
jobID := int32(555)
|
||||
|
||||
client, cleanup := mockJobServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var body slurm.JobSubmitReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
if body.Job == nil {
|
||||
t.Fatal("job desc is nil")
|
||||
}
|
||||
j := body.Job
|
||||
|
||||
// Existing fields: Script and WorkDir should be set
|
||||
if j.Script == nil || *j.Script != "echo hi" {
|
||||
t.Errorf("Script mismatch: %v", j.Script)
|
||||
}
|
||||
if j.CurrentWorkingDirectory == nil || *j.CurrentWorkingDirectory != "/home/user" {
|
||||
t.Errorf("CurrentWorkingDirectory mismatch: %v", j.CurrentWorkingDirectory)
|
||||
}
|
||||
|
||||
// All new scheduling fields should be nil/empty
|
||||
if j.MemoryPerNode != nil {
|
||||
t.Errorf("MemoryPerNode should be nil, got %v", j.MemoryPerNode)
|
||||
}
|
||||
if j.MemoryPerCpu != nil {
|
||||
t.Errorf("MemoryPerCpu should be nil, got %v", j.MemoryPerCpu)
|
||||
}
|
||||
if j.Nodes != nil {
|
||||
t.Errorf("Nodes should be nil, got %v", j.Nodes)
|
||||
}
|
||||
if j.Tasks != nil {
|
||||
t.Errorf("Tasks should be nil, got %v", j.Tasks)
|
||||
}
|
||||
if j.CpusPerTask != nil {
|
||||
t.Errorf("CpusPerTask should be nil, got %v", j.CpusPerTask)
|
||||
}
|
||||
if j.Constraints != nil {
|
||||
t.Errorf("Constraints should be nil, got %v", j.Constraints)
|
||||
}
|
||||
if j.Reservation != nil {
|
||||
t.Errorf("Reservation should be nil, got %v", j.Reservation)
|
||||
}
|
||||
if j.Account != nil {
|
||||
t.Errorf("Account should be nil, got %v", j.Account)
|
||||
}
|
||||
if j.Nice != nil {
|
||||
t.Errorf("Nice should be nil, got %v", j.Nice)
|
||||
}
|
||||
if len(j.MailType) != 0 {
|
||||
t.Errorf("MailType should be empty, got %v", j.MailType)
|
||||
}
|
||||
if j.MailUser != nil {
|
||||
t.Errorf("MailUser should be nil, got %v", j.MailUser)
|
||||
}
|
||||
if j.StandardOutput != nil {
|
||||
t.Errorf("StandardOutput should be nil, got %v", j.StandardOutput)
|
||||
}
|
||||
if j.StandardError != nil {
|
||||
t.Errorf("StandardError should be nil, got %v", j.StandardError)
|
||||
}
|
||||
if j.StandardInput != nil {
|
||||
t.Errorf("StandardInput should be nil, got %v", j.StandardInput)
|
||||
}
|
||||
if len(j.RequiredNodes) != 0 {
|
||||
t.Errorf("RequiredNodes should be empty, got %v", j.RequiredNodes)
|
||||
}
|
||||
if len(j.ExcludedNodes) != 0 {
|
||||
t.Errorf("ExcludedNodes should be empty, got %v", j.ExcludedNodes)
|
||||
}
|
||||
if j.BeginTime != nil {
|
||||
t.Errorf("BeginTime should be nil, got %v", j.BeginTime)
|
||||
}
|
||||
if j.Deadline != nil {
|
||||
t.Errorf("Deadline should be nil, got %v", j.Deadline)
|
||||
}
|
||||
if j.Array != nil {
|
||||
t.Errorf("Array should be nil, got %v", j.Array)
|
||||
}
|
||||
if j.Dependency != nil {
|
||||
t.Errorf("Dependency should be nil, got %v", j.Dependency)
|
||||
}
|
||||
if j.Requeue != nil {
|
||||
t.Errorf("Requeue should be nil, got %v", j.Requeue)
|
||||
}
|
||||
if j.KillOnNodeFail != nil {
|
||||
t.Errorf("KillOnNodeFail should be nil, got %v", j.KillOnNodeFail)
|
||||
}
|
||||
|
||||
resp := slurm.OpenapiJobSubmitResponse{
|
||||
Result: &slurm.JobSubmitResponseMsg{JobID: &jobID},
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}))
|
||||
defer cleanup()
|
||||
|
||||
svc := NewJobService(client, zap.NewNop())
|
||||
resp, err := svc.SubmitJob(context.Background(), &model.SubmitJobRequest{
|
||||
Script: "echo hi",
|
||||
WorkDir: "/home/user",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SubmitJob: %v", err)
|
||||
}
|
||||
if resp.JobID != 555 {
|
||||
t.Errorf("expected JobID 555, got %d", resp.JobID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitJob_MemoryBothSet(t *testing.T) {
|
||||
jobID := int32(777)
|
||||
memoryPerNode := int64(4096)
|
||||
memoryPerCpu := int64(1024)
|
||||
|
||||
client, cleanup := mockJobServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var body slurm.JobSubmitReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
if body.Job == nil {
|
||||
t.Fatal("job desc is nil")
|
||||
}
|
||||
j := body.Job
|
||||
|
||||
// Both memory fields should be mapped independently
|
||||
if j.MemoryPerNode == nil || j.MemoryPerNode.Number == nil || *j.MemoryPerNode.Number != memoryPerNode {
|
||||
t.Errorf("MemoryPerNode mismatch: %v", j.MemoryPerNode)
|
||||
}
|
||||
if j.MemoryPerCpu == nil || j.MemoryPerCpu.Number == nil || *j.MemoryPerCpu.Number != memoryPerCpu {
|
||||
t.Errorf("MemoryPerCpu mismatch: %v", j.MemoryPerCpu)
|
||||
}
|
||||
|
||||
resp := slurm.OpenapiJobSubmitResponse{
|
||||
Result: &slurm.JobSubmitResponseMsg{JobID: &jobID},
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}))
|
||||
defer cleanup()
|
||||
|
||||
svc := NewJobService(client, zap.NewNop())
|
||||
resp, err := svc.SubmitJob(context.Background(), &model.SubmitJobRequest{
|
||||
Script: "echo mem",
|
||||
MemoryPerNode: &memoryPerNode,
|
||||
MemoryPerCpu: &memoryPerCpu,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SubmitJob: %v", err)
|
||||
}
|
||||
if resp.JobID != 777 {
|
||||
t.Errorf("expected JobID 777, got %d", resp.JobID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetJob_FallbackToHistory_EmptyHistory(t *testing.T) {
|
||||
client, cleanup := mockJobServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
Reference in New Issue
Block a user