Repository: kubernetes-sigs/kueue

Issue:
Flaky Test: Race detection on MultiKueue AdmissionCheck controller
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!

If the matter is security related, please disclose it privately via https://kubernetes.io/security/
-->


**What happened**:

The CI detects race conditions in the following:

- `sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue: TestListMultiKueueClustersUsingKubeConfig/multiple_clusters,_single_match`

```shell
{Failed  === RUN   TestListMultiKueueClustersUsingKubeConfig/multiple_clusters,_single_match
==================
WARNING: DATA RACE
Read at 0x00c000438043 by goroutine 203:
  testing.(*common).logDepth()
      /usr/local/go/src/testing/testing.go:1053 +0xca
  testing.(*common).log()
      /usr/local/go/src/testing/testing.go:1046 +0x7b
  testing.(*common).Log()
      /usr/local/go/src/testing/testing.go:1087 +0x53
  github.com/go-logr/logr/testr.logInfo()
      /home/prow/go/src/sigs.k8s.io/kueue/vendor/github.com/go-logr/logr/testr/testr.go:101 +0x193
  github.com/go-logr/logr/testr.testloggerInterface.Info()
      /home/prow/go/src/sigs.k8s.io/kueue/vendor/github.com/go-logr/logr/testr/testr.go:148 +0x13c
  github.com/go-logr/logr/testr.(*testloggerInterface).Info()
      <autogenerated>:1 +0x147
  github.com/go-logr/logr.Logger.Info()
      /home/prow/go/src/sigs.k8s.io/kueue/vendor/github.com/go-logr/logr/logr.go:280 +0x107
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.(*KubeConfigFSWatcher).Start.func1()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch.go:92 +0x904

Previous write at 0x00c000438043 by goroutine 197:
  testing.tRunner.func1()
      /usr/local/go/src/testing/testing.go:1779 +0x8b9
  runtime.deferreturn()
      /usr/local/go/src/runtime/panic.go:610 +0x5d
  testing.(*T).Run.gowrap1()
      /usr/local/go/src/testing/testing.go:1851 +0x44

Goroutine 203 (running) created at:
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.(*KubeConfigFSWatcher).Start()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch.go:70 +0x245
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.TestFSWatchAddRm.func2()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch_test.go:227 +0x44
  sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue.TestFSWatchAddRm.func13()
      /home/prow/go/src/sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue/fswatch_test.go:452 +0x5a
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1792 +0x225
  testing.(*T).Run.gowrap1()
      /usr/local/go/src/testing/testing.go:1851 +0x44

Goroutine 197 (finished) created at:
  testing.(*T).Run()
      /usr/local/go/src/testing/testing.go:1851 +0x8f2
  testing.runTests.func1()
      /usr/local/go/src/testing/testing.go:2279 +0x85
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1792 +0x225
  testing.runTests()
      /usr/local/go/src/testing/testing.go:2277 +0x96c
  testing.(*M).Run()
      /usr/local/go/src/testing/testing.go:2142 +0xeea
  main.main()
      _testmain.go:71 +0x164
==================
    testing.go:1490: race detected during execution of test
--- FAIL: TestListMultiKueueClustersUsingKubeConfig/multiple_clusters,_single_match (0.04s)
}
```

- `sigs.k8s.io/kueue/pkg/controller/admissionchecks/multikueue: TestListMultiKueueClustersUsingKubeConfig`

```shell
{Failed  === RUN   TestListMultiKueueClustersUsingKubeConfig
--- FAIL: TestListMultiKueueClustersUsingKubeConfig (0.13s)
}
```

**What you expected to happen**:

No race condition

**How to reproduce it (as minimally and precisely as possible)**:

https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_kueue/6673/pull-kueue-test-unit-main/1960302981727391744

**Anything else we need to know?**:

**Environment**:
- Kubernetes version (use `kubectl version`):
- Kueue version (use `git describe --tags --dirty --always`):
- Cloud provider or hardware configuration:
- OS (e.g: `cat /etc/os-release`):
- Kernel (e.g. `uname -a`):
- Install tools:
- Others:

Discussion:
@tenzen-y: /kind flake@tenzen-y: Can anyone take a look at this one? It seems that this occurs frequently.
@tenzen-y: We observed this issue in the periodic test as well: https://prow.k8s.io/view/gs/kubernetes-ci-logs/logs/periodic-kueue-test-unit-main/1960409512091324416
@mbobrovskyi: /assign

Solution patch:
diff --git a/pkg/cache/snapshot.go b/pkg/cache/snapshot.go
index 4cd01c3a4..33cc9e75a 100644
--- a/pkg/cache/snapshot.go
+++ b/pkg/cache/snapshot.go
@@ -203,7 +203,7 @@ func (c *Cache) snapshotClusterQueue(ctx context.Context, cq *clusterQueue, afsE
 	for i, rg := range cq.ResourceGroups {
 		cc.ResourceGroups[i] = rg.Clone()
 	}
-	if features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(c.admissionFairSharing) {
 		if cq.AdmissionScope != nil {
 			cc.AdmissionScope = *cq.AdmissionScope.DeepCopy()
 		}
diff --git a/pkg/controller/core/localqueue_controller.go b/pkg/controller/core/localqueue_controller.go
index 572ec27fd..12982ae86 100644
--- a/pkg/controller/core/localqueue_controller.go
+++ b/pkg/controller/core/localqueue_controller.go
@@ -172,7 +172,7 @@ func (r *LocalQueueReconciler) Reconcile(ctx context.Context, req ctrl.Request)
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
 
-	if r.admissionFSConfig != nil && features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(r.admissionFSConfig) {
 		updated := r.initializeAdmissionFsStatus(&queueObj)
 		sinceLastUpdate := r.clock.Now().Sub(queueObj.Status.FairSharing.AdmissionFairSharingStatus.LastUpdate.Time)
 		lqKey := utilqueue.Key(&queueObj)
diff --git a/pkg/queue/manager.go b/pkg/queue/manager.go
index 2405541a9..a53bc709a 100644
--- a/pkg/queue/manager.go
+++ b/pkg/queue/manager.go
@@ -38,6 +38,7 @@ import (
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/hierarchy"
 	"sigs.k8s.io/kueue/pkg/metrics"
+	afs "sigs.k8s.io/kueue/pkg/util/admissionfairsharing"
 	utilmaps "sigs.k8s.io/kueue/pkg/util/maps"
 	"sigs.k8s.io/kueue/pkg/util/queue"
 	"sigs.k8s.io/kueue/pkg/workload"
@@ -61,9 +62,7 @@ func WithClock(c clock.WithDelayedExecution) Option {
 
 func WithAdmissionFairSharing(cfg *config.AdmissionFairSharing) Option {
 	return func(m *Manager) {
-		if features.Enabled(features.AdmissionFairSharing) {
-			m.admissionFairSharingConfig = cfg
-		}
+		m.admissionFairSharingConfig = cfg
 	}
 }
 
@@ -182,7 +181,7 @@ func (m *Manager) AddClusterQueue(ctx context.Context, cq *kueue.ClusterQueue) e
 	}
 
 	var afsEntryPenalties *utilmaps.SyncMap[queue.LocalQueueReference, corev1.ResourceList]
-	if features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(m.admissionFairSharingConfig) {
 		afsEntryPenalties = m.afsEntryPenalties.GetPenalties()
 	}
 	cqImpl, err := newClusterQueue(ctx, m.client, cq, m.workloadOrdering, m.admissionFairSharingConfig, afsEntryPenalties)
diff --git a/pkg/scheduler/preemption/classical/candidate_generator.go b/pkg/scheduler/preemption/classical/candidate_generator.go
index 0e6a4fe66..8e4e3a00c 100644
--- a/pkg/scheduler/preemption/classical/candidate_generator.go
+++ b/pkg/scheduler/preemption/classical/candidate_generator.go
@@ -72,19 +72,26 @@ func splitEvicted(workloads []*candidateElem) ([]*candidateElem, []*candidateEle
 // NewCandidateIterator creates a new iterator that yields candidate workloads for preemption
 // The iterator can be used to perform two independent runs over the list of candidates:
 // with and without borrowing. The runs are independent which means that the same candidates
-// might be returned for both, but note that the candidates with borrrowing are a subset of
+// might be returned for both, but note that the candidates with borrowing are a subset of
 // candidates without borrowing.
-func NewCandidateIterator(hierarchicalReclaimCtx *HierarchicalPreemptionCtx, frsNeedPreemption sets.Set[resources.FlavorResource], snapshot *cache.Snapshot, clock clock.Clock, ordering func(logr.Logger, *workload.Info, *workload.Info, kueue.ClusterQueueReference, time.Time) bool) *candidateIterator {
+func NewCandidateIterator(
+	hierarchicalReclaimCtx *HierarchicalPreemptionCtx,
+	enabledAfs bool,
+	frsNeedPreemption sets.Set[resources.FlavorResource],
+	snapshot *cache.Snapshot,
+	clock clock.Clock,
+	ordering func(logr.Logger, bool, *workload.Info, *workload.Info, kueue.ClusterQueueReference, time.Time) bool,
+) *candidateIterator {
 	sameQueueCandidates := collectSameQueueCandidates(hierarchicalReclaimCtx)
 	hierarchyCandidates, priorityCandidates := collectCandidatesForHierarchicalReclaim(hierarchicalReclaimCtx)
 	sort.Slice(sameQueueCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, sameQueueCandidates[i].wl, sameQueueCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, sameQueueCandidates[i].wl, sameQueueCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 	sort.Slice(priorityCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, priorityCandidates[i].wl, priorityCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, priorityCandidates[i].wl, priorityCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 	sort.Slice(hierarchyCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, hierarchyCandidates[i].wl, hierarchyCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, hierarchyCandidates[i].wl, hierarchyCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 
 	evictedHierarchicalReclaimCandidates, nonEvictedHierarchicalReclaimCandidates := splitEvicted(hierarchyCandidates)
diff --git a/pkg/scheduler/preemption/preemption.go b/pkg/scheduler/preemption/preemption.go
index 35f41abd1..e5651bfba 100644
--- a/pkg/scheduler/preemption/preemption.go
+++ b/pkg/scheduler/preemption/preemption.go
@@ -39,7 +39,6 @@ import (
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
 	"sigs.k8s.io/kueue/pkg/cache"
 	"sigs.k8s.io/kueue/pkg/controller/constants"
-	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/resources"
 	"sigs.k8s.io/kueue/pkg/scheduler/flavorassigner"
 	"sigs.k8s.io/kueue/pkg/scheduler/preemption/classical"
@@ -64,6 +63,8 @@ type Preemptor struct {
 
 	// stubs
 	applyPreemption func(ctx context.Context, w *kueue.Workload, reason, message string) error
+
+	enabledAfs bool
 }
 
 type preemptionCtx struct {
@@ -81,6 +82,7 @@ func New(
 	workloadOrdering workload.Ordering,
 	recorder record.EventRecorder,
 	fs config.FairSharing,
+	enabledAfs bool,
 	clock clock.Clock,
 ) *Preemptor {
 	p := &Preemptor{
@@ -90,6 +92,7 @@ func New(
 		workloadOrdering:  workloadOrdering,
 		enableFairSharing: fs.Enable,
 		fsStrategies:      parseStrategies(fs.PreemptionStrategies),
+		enabledAfs:        enabledAfs,
 	}
 	p.applyPreemption = p.applyPreemptionWithSSA
 	return p
@@ -218,7 +221,7 @@ func (p *Preemptor) classicalPreemptions(preemptionCtx *preemptionCtx) []*Target
 		Requests:          preemptionCtx.workloadUsage.Quota,
 		WorkloadOrdering:  p.workloadOrdering,
 	}
-	candidatesGenerator := classical.NewCandidateIterator(hierarchicalReclaimCtx, preemptionCtx.frsNeedPreemption, preemptionCtx.snapshot, p.clock, CandidatesOrdering)
+	candidatesGenerator := classical.NewCandidateIterator(hierarchicalReclaimCtx, p.enabledAfs, preemptionCtx.frsNeedPreemption, preemptionCtx.snapshot, p.clock, CandidatesOrdering)
 	var attemptPossibleOpts []preemptionAttemptOpts
 	borrowWithinCohortForbidden, _ := classical.IsBorrowingWithinCohortForbidden(preemptionCtx.preemptorCQ)
 	// We have three types of candidates:
@@ -384,7 +387,7 @@ func (p *Preemptor) fairPreemptions(preemptionCtx *preemptionCtx, strategies []f
 		return nil
 	}
 	sort.Slice(candidates, func(i, j int) bool {
-		return CandidatesOrdering(preemptionCtx.log, candidates[i], candidates[j], preemptionCtx.preemptorCQ.Name, p.clock.Now())
+		return CandidatesOrdering(preemptionCtx.log, p.enabledAfs, candidates[i], candidates[j], preemptionCtx.preemptorCQ.Name, p.clock.Now())
 	})
 	if logV := preemptionCtx.log.V(5); logV.Enabled() {
 		logV.Info("Simulating fair preemption", "candidates", workload.References(candidates), "resourcesRequiringPreemption", preemptionCtx.frsNeedPreemption.UnsortedList(), "preemptingWorkload", klog.KObj(preemptionCtx.preemptor.Obj))
@@ -556,14 +559,14 @@ func resourceUsagePreemptionEnabled(a, b *workload.Info) bool {
 	return a.ClusterQueue == b.ClusterQueue && a.Obj.Spec.QueueName != b.Obj.Spec.QueueName && a.LocalQueueFSUsage != nil && b.LocalQueueFSUsage != nil
 }
 
-// candidatesOrdering criteria:
+// CandidatesOrdering criteria:
 // 0. Workloads already marked for preemption first.
 // 1. Workloads from other ClusterQueues in the cohort before the ones in the
 // same ClusterQueue as the preemptor.
 // 2. (AdmissionFairSharing only) Workloads with lower LocalQueue's usage first
 // 3. Workloads with lower priority first.
 // 4. Workloads admitted more recently first.
-func CandidatesOrdering(log logr.Logger, a, b *workload.Info, cq kueue.ClusterQueueReference, now time.Time) bool {
+func CandidatesOrdering(log logr.Logger, afsEnabled bool, a, b *workload.Info, cq kueue.ClusterQueueReference, now time.Time) bool {
 	aEvicted := meta.IsStatusConditionTrue(a.Obj.Status.Conditions, kueue.WorkloadEvicted)
 	bEvicted := meta.IsStatusConditionTrue(b.Obj.Status.Conditions, kueue.WorkloadEvicted)
 	if aEvicted != bEvicted {
@@ -575,7 +578,7 @@ func CandidatesOrdering(log logr.Logger, a, b *workload.Info, cq kueue.ClusterQu
 		return !aInCQ
 	}
 
-	if features.Enabled(features.AdmissionFairSharing) && resourceUsagePreemptionEnabled(a, b) {
+	if afsEnabled && resourceUsagePreemptionEnabled(a, b) {
 		if a.LocalQueueFSUsage != b.LocalQueueFSUsage {
 			log.V(3).Info("Comparing workloads by LocalQueue fair sharing usage",
 				"workloadA", klog.KObj(a.Obj), "queueA", a.Obj.Spec.QueueName, "usageA", a.LocalQueueFSUsage,
diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go
index 4c1d61087..9f0e1da8b 100644
--- a/pkg/scheduler/scheduler.go
+++ b/pkg/scheduler/scheduler.go
@@ -145,7 +145,7 @@ func New(queues *queue.Manager, cache *cache.Cache, cl client.Client, recorder r
 		cache:                   cache,
 		client:                  cl,
 		recorder:                recorder,
-		preemptor:               preemption.New(cl, wo, recorder, options.fairSharing, options.clock),
+		preemptor:               preemption.New(cl, wo, recorder, options.fairSharing, afs.Enabled(options.admissionFairSharing), options.clock),
 		admissionRoutineWrapper: routine.DefaultWrapper,
 		workloadOrdering:        wo,
 		clock:                   options.clock,
@@ -195,7 +195,7 @@ func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
 	ctx = ctrl.LoggerInto(ctx, log)
 
 	var snapshotOpts []cache.SnapshotOption
-	if features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(s.admissionFairSharing) {
 		s.queues.RebuildClusterQueuesWithEntryPenalties()
 		snapshotOpts = append(snapshotOpts, cache.WithAfsEntryPenalties(s.queues.GetAfsEntryPenalties()))
 	}
@@ -623,7 +623,7 @@ func (s *Scheduler) admit(ctx context.Context, e *entry, cq *cache.ClusterQueueS
 	e.status = assumed
 	log.V(2).Info("Workload assumed in the cache")
 
-	if s.admissionFairSharing != nil && features.Enabled(features.AdmissionFairSharing) {
+	if afs.Enabled(s.admissionFairSharing) {
 		s.updateEntryPenalty(log, e, add)
 
 		// Trigger LocalQueue reconciler to apply any pending penalties
@@ -642,7 +642,7 @@ func (s *Scheduler) admit(ctx context.Context, e *entry, cq *cache.ClusterQueueS
 		// Ignore errors because the workload or clusterQueue could have been deleted
 		// by an event.
 		_ = s.cache.ForgetWorkload(log, newWorkload)
-		if s.admissionFairSharing != nil && features.Enabled(features.AdmissionFairSharing) {
+		if afs.Enabled(s.admissionFairSharing) {
 			s.updateEntryPenalty(log, e, subtract)
 		}
 		if apierrors.IsNotFound(err) {
diff --git a/pkg/util/admissionfairsharing/admission_fair_sharing.go b/pkg/util/admissionfairsharing/admission_fair_sharing.go
index ef2a873a4..b12298b68 100644
--- a/pkg/util/admissionfairsharing/admission_fair_sharing.go
+++ b/pkg/util/admissionfairsharing/admission_fair_sharing.go
@@ -29,7 +29,7 @@ import (
 
 func ResourceWeights(cqAdmissionScope *kueue.AdmissionScope, afsConfig *config.AdmissionFairSharing) (bool, map[corev1.ResourceName]float64) {
 	enableAdmissionFs, fsResWeights := false, make(map[corev1.ResourceName]float64)
-	if features.Enabled(features.AdmissionFairSharing) && afsConfig != nil && cqAdmissionScope != nil && cqAdmissionScope.AdmissionMode == kueue.UsageBasedAdmissionFairSharing {
+	if Enabled(afsConfig) && cqAdmissionScope != nil && cqAdmissionScope.AdmissionMode == kueue.UsageBasedAdmissionFairSharing {
 		enableAdmissionFs = true
 		fsResWeights = afsConfig.ResourceWeights
 	}
@@ -52,3 +52,7 @@ func CalculateEntryPenalty(totalRequests corev1.ResourceList, afs *config.Admiss
 
 	return resource.MulByFloat(totalRequests, alpha)
 }
+
+func Enabled(afsConfig *config.AdmissionFairSharing) bool {
+	return afsConfig != nil && features.Enabled(features.AdmissionFairSharing)
+}



Test patch:
diff --git a/pkg/scheduler/preemption/preemption_test.go b/pkg/scheduler/preemption/preemption_test.go
index 66dc406f5..8b323eead 100644
--- a/pkg/scheduler/preemption/preemption_test.go
+++ b/pkg/scheduler/preemption/preemption_test.go
@@ -33,7 +33,6 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/component-base/featuregate"
 	clocktesting "k8s.io/utils/clock/testing"
 	"k8s.io/utils/ptr"
 
@@ -1838,7 +1837,7 @@ func TestPreemption(t *testing.T) {
 				t.Fatalf("Failed adding kueue scheme: %v", err)
 			}
 			recorder := broadcaster.NewRecorder(scheme, corev1.EventSource{Component: constants.AdmissionName})
-			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, clocktesting.NewFakeClock(now))
+			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, false, clocktesting.NewFakeClock(now))
 			preemptor.applyPreemption = func(ctx context.Context, w *kueue.Workload, reason, _ string) error {
 				lock.Lock()
 				gotPreempted.Insert(targetKeyReason(workload.Key(w), reason))
@@ -2754,7 +2753,7 @@ func TestFairPreemptions(t *testing.T) {
 			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{
 				Enable:               true,
 				PreemptionStrategies: tc.strategies,
-			}, clocktesting.NewFakeClock(now))
+			}, false, clocktesting.NewFakeClock(now))
 
 			beforeSnapshot, err := cqCache.Snapshot(ctx)
 			if err != nil {
@@ -2817,9 +2816,9 @@ func TestCandidatesOrdering(t *testing.T) {
 	wlHighUsageLqDifCQ.LocalQueueFSUsage = ptr.To(1.0)
 
 	cases := map[string]struct {
-		candidates         []workload.Info
-		wantCandidates     []workload.Reference
-		enableFeatureGates []featuregate.Feature
+		candidates                  []workload.Info
+		wantCandidates              []workload.Reference
+		admissionFairSharingEnabled bool
 	}{
 		"workloads sorted by priority": {
 			candidates: []workload.Info{
@@ -2882,25 +2881,23 @@ func TestCandidatesOrdering(t *testing.T) {
 				*wlLowUsageLq,
 				*wlMidUsageLq,
 			},
-			wantCandidates:     []workload.Reference{"mid_lq_usage", "low_lq_usage"},
-			enableFeatureGates: []featuregate.Feature{features.AdmissionFairSharing},
+			wantCandidates:              []workload.Reference{"mid_lq_usage", "low_lq_usage"},
+			admissionFairSharingEnabled: true,
 		},
 		"workloads from different CQ are sorted based on priority and timestamp": {
 			candidates: []workload.Info{
 				*wlMidUsageLq,
 				*wlHighUsageLqDifCQ,
 			},
-			wantCandidates:     []workload.Reference{"high_lq_usage_different_cq", "mid_lq_usage"},
-			enableFeatureGates: []featuregate.Feature{features.AdmissionFairSharing},
+			wantCandidates:              []workload.Reference{"high_lq_usage_different_cq", "mid_lq_usage"},
+			admissionFairSharingEnabled: true,
 		}}
 
 	_, log := utiltesting.ContextWithLog(t)
 	for _, tc := range cases {
-		for _, gate := range tc.enableFeatureGates {
-			features.SetFeatureGateDuringTest(t, gate, true)
-		}
+		features.SetFeatureGateDuringTest(t, features.AdmissionFairSharing, tc.admissionFairSharingEnabled)
 		sort.Slice(tc.candidates, func(i int, j int) bool {
-			return CandidatesOrdering(log, &tc.candidates[i], &tc.candidates[j], kueue.ClusterQueueReference(preemptorCq), now)
+			return CandidatesOrdering(log, tc.admissionFairSharingEnabled, &tc.candidates[i], &tc.candidates[j], kueue.ClusterQueueReference(preemptorCq), now)
 		})
 		got := slices.Map(tc.candidates, func(c *workload.Info) workload.Reference {
 			return workload.Reference(c.Obj.Name)
@@ -4124,7 +4121,7 @@ func TestHierarchicalPreemptions(t *testing.T) {
 				t.Fatalf("Failed adding kueue scheme: %v", err)
 			}
 			recorder := broadcaster.NewRecorder(scheme, corev1.EventSource{Component: constants.AdmissionName})
-			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, clocktesting.NewFakeClock(now))
+			preemptor := New(cl, workload.Ordering{}, recorder, config.FairSharing{}, false, clocktesting.NewFakeClock(now))
 			preemptor.applyPreemption = func(ctx context.Context, w *kueue.Workload, reason, _ string) error {
 				lock.Lock()
 				gotPreempted.Insert(targetKeyReason(workload.Key(w), reason))