Repository: kubernetes-sigs/kueue

Issue:
TAS: Implement e2e tests for RayJob with TAS
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:
E2e tests for RayJob with TAS

**Why is this needed**:

Discussed here as well: https://github.com/kubernetes-sigs/kueue/pull/3704#discussion_r1866116694

Hints from issue comments:
@PBundyra: /cc @mbobrovskyi @mszadkow: /assign@k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@mimowo: /remove-lifecycle stale
It is being done as part of https://github.com/kubernetes-sigs/kueue/pull/4341

Solution patch:
diff --git a/cmd/experimental/kueue-viz/frontend/package.json b/cmd/experimental/kueue-viz/frontend/package.json
index ed11c794b..172e22de2 100644
--- a/cmd/experimental/kueue-viz/frontend/package.json
+++ b/cmd/experimental/kueue-viz/frontend/package.json
@@ -11,8 +11,8 @@
   "dependencies": {
     "@emotion/react": "^11.0.0",
     "@emotion/styled": "^11.0.0",
-    "@mui/icons-material": "^5.0.0",
-    "@mui/material": "^5.0.0",
+    "@mui/icons-material": "^6.4.6",
+    "@mui/material": "^6.4.6",
     "axios": "^1.7.9",
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go
index 214b912bf..7a530ee68 100644
--- a/pkg/scheduler/scheduler.go
+++ b/pkg/scheduler/scheduler.go
@@ -198,12 +198,8 @@ func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
 	// 3. Calculate requirements (resource flavors, borrowing) for admitting workloads.
 	entries := s.nominate(ctx, headWorkloads, snapshot)
 
-	// 4. Sort entries based on borrowing, priorities (if enabled) and timestamps.
-	sort.Sort(entryOrdering{
-		enableFairSharing: s.fairSharing.Enable,
-		entries:           entries,
-		workloadOrdering:  s.workloadOrdering,
-	})
+	// 4. Create iterator which returns ordered entries.
+	var iterator entryIterator = makeIterator(entries, s.workloadOrdering, s.fairSharing.Enable)
 
 	// 5. Admit entries, ensuring that no more than one workload gets
 	// admitted by a cohort (if borrowing).
@@ -212,8 +208,8 @@ func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
 	// of other clusterQueues.
 	preemptedWorkloads := make(preemptedWorkloads)
 	skippedPreemptions := make(map[string]int)
-	for i := range entries {
-		e := &entries[i]
+	for iterator.hasNext() {
+		e := iterator.pop()
 		mode := e.assignment.RepresentativeMode()
 		if mode == flavorassigner.NoFit {
 			continue
@@ -532,11 +528,7 @@ func (e entryOrdering) Swap(i, j int) {
 	e.entries[i], e.entries[j] = e.entries[j], e.entries[i]
 }
 
-// Less is the ordering criteria:
-// 1. request under nominal quota before borrowing.
-// 2. fair sharing: lower DominantResourceShare first.
-// 3. higher priority first.
-// 4. FIFO on eviction or creation timestamp.
+// Less is the ordering criteria
 func (e entryOrdering) Less(i, j int) bool {
 	a := e.entries[i]
 	b := e.entries[j]
@@ -568,6 +560,43 @@ func (e entryOrdering) Less(i, j int) bool {
 	return aComparisonTimestamp.Before(bComparisonTimestamp)
 }
 
+// entryInterator defines order that entries are returned.
+// pop->nil IFF hasNext->False
+type entryIterator interface {
+	pop() *entry
+	hasNext() bool
+}
+
+// classicalIterator returns entries ordered on:
+// 1. request under nominal quota before borrowing.
+// 2. fair sharing: lower DominantResourceShare first.
+// 3. higher priority first.
+// 4. FIFO on eviction or creation timestamp.
+type classicalIterator struct {
+	entries []entry
+}
+
+func (co *classicalIterator) hasNext() bool {
+	return len(co.entries) > 0
+}
+
+func (co *classicalIterator) pop() *entry {
+	head := &co.entries[0]
+	co.entries = co.entries[1:]
+	return head
+}
+
+func makeIterator(entries []entry, workloadOrdering workload.Ordering, enableFairSharing bool) *classicalIterator {
+	sort.Sort(entryOrdering{
+		entries:           entries,
+		enableFairSharing: enableFairSharing,
+		workloadOrdering:  workloadOrdering,
+	})
+	return &classicalIterator{
+		entries: entries,
+	}
+}
+
 func (s *Scheduler) requeueAndUpdate(ctx context.Context, e entry) {
 	log := ctrl.LoggerFrom(ctx)
 	if e.status != notNominated && e.requeueReason == queue.RequeueReasonGeneric {



Test patch:
diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go
index e42b8ce27..bf431d3a2 100644
--- a/pkg/scheduler/scheduler_test.go
+++ b/pkg/scheduler/scheduler_test.go
@@ -21,7 +21,6 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
-	"sort"
 	"sync"
 	"testing"
 	"time"
@@ -2964,13 +2963,13 @@ func TestEntryOrdering(t *testing.T) {
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			features.SetFeatureGateDuringTest(t, features.PrioritySortingWithinCohort, tc.prioritySorting)
-			sort.Sort(entryOrdering{
-				entries:          tc.input,
-				workloadOrdering: tc.workloadOrdering},
-			)
+			iter := makeIterator(tc.input, tc.workloadOrdering, false)
 			order := make([]string, len(tc.input))
-			for i, e := range tc.input {
-				order[i] = e.Obj.Name
+			for i := range tc.input {
+				order[i] = iter.pop().Obj.Name
+			}
+			if iter.hasNext() {
+				t.Error("Expected iterator to be exhausted")
 			}
 			if diff := cmp.Diff(tc.wantOrder, order); diff != "" {
 				t.Errorf("%s: Unexpected order (-want,+got):\n%s", tc.name, diff)
diff --git a/test/e2e/tas/rayjob_test.go b/test/e2e/tas/rayjob_test.go
index 22fa4c497..b23eedcdf 100644
--- a/test/e2e/tas/rayjob_test.go
+++ b/test/e2e/tas/rayjob_test.go
@@ -83,6 +83,7 @@ var _ = ginkgo.Describe("TopologyAwareScheduling for RayJob", ginkgo.Ordered, fu
 		util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
 		util.ExpectObjectToBeDeleted(ctx, k8sClient, tasFlavor, true)
 		util.ExpectObjectToBeDeleted(ctx, k8sClient, topology, true)
+		util.ExpectAllPodsInNamespaceDeleted(ctx, k8sClient, ns)
 	})
 
 	ginkgo.When("Creating a RayJob", func() {