feat(job-array-one-based-index): control by filter with element selections (task 7.5)
Filter-based ControlJob validates a bracketed -J value before fetching (400 with the parser's message; unbracketed names, including commands with spaces, unchanged). FetchJobList evaluates bracketed job_name entries through the selection predicates instead of unevaluated compound conditions, so -J 'foo[1]' acts on element 1 of every array named foo and never on ordinary jobs of that name. The most-recent-job rule applies only without an element number and without all-jobs, and treats the element of a one-element array like an ordinary job (isSingleElementArray, a documented approximation). The old index-section parser is deleted. Completes bug-1898 on the owned paths. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db16b565dd
commit
40917a01f0
5 changed files with 251 additions and 134 deletions
|
|
@ -1234,3 +1234,152 @@ func TestJobHandler_ControlValidThenInvalid(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Control by filter with element selections (task 7.5) ==========
|
||||
|
||||
// controlFilterKill posts a kill through the filter-based ControlJob with
|
||||
// the job-name filter name (the -J value as the client sends it).
|
||||
func (f *handlerFixture) controlFilterKill(name string) *httptest.ResponseRecorder {
|
||||
f.t.Helper()
|
||||
return f.controlByFilter(models.JobControlRequest{
|
||||
Filter: models.JobControlFilter{JobName: name},
|
||||
Action: "kill",
|
||||
})
|
||||
}
|
||||
|
||||
// TestJobHandler_ControlFilterRejectsBadSelection (5.7, 5.16, 5.5): a
|
||||
// bracketed -J value the name parser rejects returns 400 with its specific
|
||||
// message before any job is fetched, and dispatches nothing.
|
||||
func TestJobHandler_ControlFilterRejectsBadSelection(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
X := handlerTestArrayBase + 40<<15
|
||||
elems := f.seedArrayElements(X, "foo", "RUN", 1, 2)
|
||||
assertSeeded(t, uint64(elems[0].Unique_JobID))
|
||||
|
||||
cases := []struct{ name, want string }{
|
||||
{"foo[40000]", "element numbers must be at most"},
|
||||
{"foo[3-1]", "reversed"},
|
||||
{"foo[a]", "invalid"},
|
||||
{"foo [1]", "invalid"},
|
||||
{"foo[1", "invalid"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
f.dispatched = nil
|
||||
assert400(t, "-J "+c.name, f.controlFilterKill(c.name), c.want)
|
||||
if got := f.dispatchedIDs(); len(got) != 0 {
|
||||
t.Errorf("-J %s: dispatched %v, want none", c.name, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestJobHandler_ControlFilterEmptyNameSelectsNothing (design ApiMatch):
|
||||
// -J '[1]' (control sends eq) matches no row, not the unnamed array.
|
||||
func TestJobHandler_ControlFilterEmptyNameSelectsNothing(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
Z := handlerTestArrayBase + 41<<15
|
||||
elems := f.seedArrayElements(Z, "", "RUN", 1)
|
||||
assertSeeded(t, uint64(elems[0].Unique_JobID))
|
||||
|
||||
w := f.controlFilterKill("[1]")
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if got := f.dispatchedIDs(); len(got) != 0 {
|
||||
t.Errorf("-J [1] dispatched %v, want none", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestJobHandler_ControlFilterMostRecentOneElementArray (5.10): with no
|
||||
// element number in the filter, when the newest matching job is the element
|
||||
// of a one-element array, only that element is acted on.
|
||||
func TestJobHandler_ControlFilterMostRecentOneElementArray(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
O := handlerTestOrdinaryBase + 50
|
||||
f.seedOrdinaryJob(O, "foo", "RUN")
|
||||
A := handlerTestArrayBase + 42<<15
|
||||
a1 := backend.MakeElementID(A, 1)
|
||||
f.seedArrayElements(A, "foo", "RUN", 1)
|
||||
assertSeeded(t, O, a1)
|
||||
|
||||
r := decodeControlReply(t, f.controlFilterKill("foo"))
|
||||
assertExactIDs(t, "-J foo dispatched", f.dispatchedIDs(), []uint64{a1})
|
||||
assertExactIDs(t, "-J foo accepted", itemIDs(t, r.Accepted), []uint64{a1})
|
||||
|
||||
// all-jobs: the rule does not apply, every matching job is acted on
|
||||
f.dispatched = nil
|
||||
w := f.controlByFilter(models.JobControlRequest{
|
||||
Filter: models.JobControlFilter{JobName: "foo", AllJobs: true},
|
||||
Action: "kill",
|
||||
})
|
||||
decodeControlReply(t, w)
|
||||
assertExactIDs(t, "-J foo -a dispatched", f.dispatchedIDs(), []uint64{O, a1})
|
||||
}
|
||||
|
||||
// TestJobHandler_ControlFilterElementSelection (5.2): -J 'foo[1]' acts on
|
||||
// exactly element 1 of every array named foo -- both one-element arrays and
|
||||
// element 1 of a larger one -- never on an ordinary job named foo, another
|
||||
// element, or another name, whatever the newest row is.
|
||||
func TestJobHandler_ControlFilterElementSelection(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
O := handlerTestOrdinaryBase + 51
|
||||
f.seedOrdinaryJob(O, "foo", "RUN")
|
||||
A := handlerTestArrayBase + 43<<15
|
||||
B := handlerTestArrayBase + 44<<15
|
||||
C := handlerTestArrayBase + 45<<15
|
||||
D := handlerTestArrayBase + 46<<15
|
||||
f.seedArrayElements(A, "foo", "RUN", 1)
|
||||
f.seedArrayElements(B, "foo", "RUN", 1)
|
||||
f.seedArrayElements(C, "foo", "RUN", 1, 2)
|
||||
f.seedArrayElements(D, "bar", "RUN", 1)
|
||||
a1, b1, c1 := backend.MakeElementID(A, 1), backend.MakeElementID(B, 1), backend.MakeElementID(C, 1)
|
||||
assertSeeded(t, O, a1, b1, c1, backend.MakeElementID(C, 2), backend.MakeElementID(D, 1))
|
||||
|
||||
r := decodeControlReply(t, f.controlFilterKill("foo[1]"))
|
||||
assertExactIDs(t, "-J foo[1] dispatched", f.dispatchedIDs(), []uint64{a1, b1, c1})
|
||||
assertExactIDs(t, "-J foo[1] accepted", itemIDs(t, r.Accepted), []uint64{a1, b1, c1})
|
||||
|
||||
// the range form 1-1 selects the same element 1 of every array named foo
|
||||
f.dispatched = nil
|
||||
r = decodeControlReply(t, f.controlFilterKill("foo[1-1]"))
|
||||
assertExactIDs(t, "-J foo[1-1] dispatched", f.dispatchedIDs(), []uint64{a1, b1, c1})
|
||||
}
|
||||
|
||||
// TestJobHandler_ControlFilterElementSelectionNewestSingleElement (5.2,
|
||||
// 5.10): when the most recent job named foo is the element of a one-element
|
||||
// array, -J 'foo[1]' still acts on element 1 of every array named foo (the
|
||||
// most-recent rule applies only to filters without an element number),
|
||||
// while -J 'foo' acts on that most recent element alone.
|
||||
func TestJobHandler_ControlFilterElementSelectionNewestSingleElement(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
O := handlerTestOrdinaryBase + 53
|
||||
f.seedOrdinaryJob(O, "foo", "RUN")
|
||||
A := handlerTestArrayBase + 47<<15
|
||||
B := handlerTestArrayBase + 48<<15 // highest ID: the newest row
|
||||
f.seedArrayElements(A, "foo", "RUN", 1)
|
||||
f.seedArrayElements(B, "foo", "RUN", 1)
|
||||
a1, b1 := backend.MakeElementID(A, 1), backend.MakeElementID(B, 1)
|
||||
assertSeeded(t, O, a1, b1)
|
||||
|
||||
r := decodeControlReply(t, f.controlFilterKill("foo[1]"))
|
||||
assertExactIDs(t, "-J foo[1] dispatched", f.dispatchedIDs(), []uint64{a1, b1})
|
||||
assertExactIDs(t, "-J foo[1] accepted", itemIDs(t, r.Accepted), []uint64{a1, b1})
|
||||
|
||||
f.dispatched = nil
|
||||
r = decodeControlReply(t, f.controlFilterKill("foo"))
|
||||
assertExactIDs(t, "-J foo dispatched", f.dispatchedIDs(), []uint64{b1})
|
||||
assertExactIDs(t, "-J foo accepted", itemIDs(t, r.Accepted), []uint64{b1})
|
||||
}
|
||||
|
||||
// TestJobHandler_ControlFilterPlainNameWithSpaces: an unnamed job carries
|
||||
// its command as its name; kill -J 'sleep 100' keeps working (no brackets,
|
||||
// no selection parsing).
|
||||
func TestJobHandler_ControlFilterPlainNameWithSpaces(t *testing.T) {
|
||||
f := newHandlerFixture(t)
|
||||
O := handlerTestOrdinaryBase + 52
|
||||
f.seedOrdinaryJob(O, "sleep 100", "RUN")
|
||||
f.seedOrdinaryJob(O+1, "sleep 200", "RUN")
|
||||
|
||||
r := decodeControlReply(t, f.controlFilterKill("sleep 100"))
|
||||
assertExactIDs(t, "-J 'sleep 100' dispatched", f.dispatchedIDs(), []uint64{O})
|
||||
assertExactIDs(t, "-J 'sleep 100' accepted", itemIDs(t, r.Accepted), []uint64{O})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue