...

Source file src/sigs.k8s.io/cli-utils/pkg/apply/solver/solver_test.go

Documentation: sigs.k8s.io/cli-utils/pkg/apply/solver

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package solver
     5  
     6  import (
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/google/go-cmp/cmp/cmpopts"
    12  	"github.com/stretchr/testify/assert"
    13  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    14  	"sigs.k8s.io/cli-utils/pkg/apis/actuation"
    15  	"sigs.k8s.io/cli-utils/pkg/apply/prune"
    16  	"sigs.k8s.io/cli-utils/pkg/apply/task"
    17  	"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
    18  	"sigs.k8s.io/cli-utils/pkg/common"
    19  	"sigs.k8s.io/cli-utils/pkg/inventory"
    20  	"sigs.k8s.io/cli-utils/pkg/object"
    21  	"sigs.k8s.io/cli-utils/pkg/object/graph"
    22  	"sigs.k8s.io/cli-utils/pkg/object/validation"
    23  	"sigs.k8s.io/cli-utils/pkg/testutil"
    24  )
    25  
    26  var (
    27  	pruner    = &prune.Pruner{}
    28  	resources = map[string]string{
    29  		"pod": `
    30  kind: Pod
    31  apiVersion: v1
    32  metadata:
    33    name: test-pod
    34    namespace: test-namespace
    35  `,
    36  		"default-pod": `
    37  kind: Pod
    38  apiVersion: v1
    39  metadata:
    40    name: pod-in-default-namespace
    41    namespace: default
    42  `,
    43  		"deployment": `
    44  kind: Deployment
    45  apiVersion: apps/v1
    46  metadata:
    47    name: foo
    48    namespace: test-namespace
    49    uid: dep-uid
    50    generation: 1
    51  spec:
    52    replicas: 1
    53  `,
    54  		"secret": `
    55  kind: Secret
    56  apiVersion: v1
    57  metadata:
    58    name: secret
    59    namespace: test-namespace
    60    uid: secret-uid
    61    generation: 1
    62  type: Opaque
    63  spec:
    64    foo: bar
    65  `,
    66  		"namespace": `
    67  kind: Namespace
    68  apiVersion: v1
    69  metadata:
    70    name: test-namespace
    71  `,
    72  
    73  		"crd": `
    74  apiVersion: apiextensions.k8s.io/v1
    75  kind: CustomResourceDefinition
    76  metadata:
    77    name: crontabs.stable.example.com
    78  spec:
    79    group: stable.example.com
    80    versions:
    81      - name: v1
    82        served: true
    83        storage: true
    84    scope: Namespaced
    85    names:
    86      plural: crontabs
    87      singular: crontab
    88      kind: CronTab
    89  `,
    90  		"crontab1": `
    91  apiVersion: "stable.example.com/v1"
    92  kind: CronTab
    93  metadata:
    94    name: cron-tab-01
    95    namespace: test-namespace
    96  `,
    97  		"crontab2": `
    98  apiVersion: "stable.example.com/v1"
    99  kind: CronTab
   100  metadata:
   101    name: cron-tab-02
   102    namespace: test-namespace
   103  `,
   104  	}
   105  )
   106  
   107  func newInvObject(name, namespace, inventoryID string) *unstructured.Unstructured {
   108  	return &unstructured.Unstructured{
   109  		Object: map[string]interface{}{
   110  			"apiVersion": "v1",
   111  			"kind":       "ConfigMap",
   112  			"metadata": map[string]interface{}{
   113  				"name":      name,
   114  				"namespace": namespace,
   115  				"labels": map[string]interface{}{
   116  					common.InventoryLabel: inventoryID,
   117  				},
   118  			},
   119  			"data": map[string]string{},
   120  		},
   121  	}
   122  }
   123  
   124  func TestTaskQueueBuilder_ApplyBuild(t *testing.T) {
   125  	// Use a custom Asserter to customize the comparison options
   126  	asserter := testutil.NewAsserter(
   127  		cmpopts.EquateErrors(),
   128  		waitTaskComparer(),
   129  		fakeClientComparer(),
   130  		inventoryInfoComparer(),
   131  	)
   132  
   133  	invInfo := inventory.WrapInventoryInfoObj(newInvObject(
   134  		"abc-123", "default", "test"))
   135  
   136  	testCases := map[string]struct {
   137  		applyObjs      []*unstructured.Unstructured
   138  		options        Options
   139  		expectedTasks  []taskrunner.Task
   140  		expectedError  error
   141  		expectedStatus []actuation.ObjectStatus
   142  	}{
   143  		"no resources, no apply or wait tasks": {
   144  			applyObjs: []*unstructured.Unstructured{},
   145  			expectedTasks: []taskrunner.Task{
   146  				&task.InvAddTask{
   147  					TaskName:  "inventory-add-0",
   148  					InvClient: &inventory.FakeClient{},
   149  					InvInfo:   invInfo,
   150  					Objects:   object.UnstructuredSet{},
   151  				},
   152  				&task.InvSetTask{
   153  					TaskName:      "inventory-set-0",
   154  					InvClient:     &inventory.FakeClient{},
   155  					InvInfo:       invInfo,
   156  					PrevInventory: object.ObjMetadataSet{},
   157  				},
   158  			},
   159  		},
   160  		"single resource, one apply task, one wait task": {
   161  			applyObjs: []*unstructured.Unstructured{
   162  				testutil.Unstructured(t, resources["deployment"]),
   163  			},
   164  			expectedTasks: []taskrunner.Task{
   165  				&task.InvAddTask{
   166  					TaskName:  "inventory-add-0",
   167  					InvClient: &inventory.FakeClient{},
   168  					InvInfo:   invInfo,
   169  					Objects: object.UnstructuredSet{
   170  						testutil.Unstructured(t, resources["deployment"]),
   171  					},
   172  				},
   173  				&task.ApplyTask{
   174  					TaskName: "apply-0",
   175  					Objects: []*unstructured.Unstructured{
   176  						testutil.Unstructured(t, resources["deployment"]),
   177  					},
   178  				},
   179  				&taskrunner.WaitTask{
   180  					TaskName: "wait-0",
   181  					Ids: object.ObjMetadataSet{
   182  						testutil.ToIdentifier(t, resources["deployment"]),
   183  					},
   184  					Condition: taskrunner.AllCurrent,
   185  				},
   186  				&task.InvSetTask{
   187  					TaskName:  "inventory-set-0",
   188  					InvClient: &inventory.FakeClient{},
   189  					InvInfo:   invInfo,
   190  					PrevInventory: object.ObjMetadataSet{
   191  						testutil.ToIdentifier(t, resources["deployment"]),
   192  					},
   193  				},
   194  			},
   195  			expectedStatus: []actuation.ObjectStatus{
   196  				{
   197  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   198  						testutil.ToIdentifier(t, resources["deployment"]),
   199  					),
   200  					Strategy:  actuation.ActuationStrategyApply,
   201  					Actuation: actuation.ActuationPending,
   202  					Reconcile: actuation.ReconcilePending,
   203  				},
   204  			},
   205  		},
   206  		"multiple resource with no timeout": {
   207  			applyObjs: []*unstructured.Unstructured{
   208  				testutil.Unstructured(t, resources["deployment"]),
   209  				testutil.Unstructured(t, resources["secret"]),
   210  			},
   211  			expectedTasks: []taskrunner.Task{
   212  				&task.InvAddTask{
   213  					TaskName:  "inventory-add-0",
   214  					InvClient: &inventory.FakeClient{},
   215  					InvInfo:   invInfo,
   216  					Objects: object.UnstructuredSet{
   217  						testutil.Unstructured(t, resources["deployment"]),
   218  						testutil.Unstructured(t, resources["secret"]),
   219  					},
   220  				},
   221  				&task.ApplyTask{
   222  					TaskName: "apply-0",
   223  					Objects: []*unstructured.Unstructured{
   224  						testutil.Unstructured(t, resources["deployment"]),
   225  						testutil.Unstructured(t, resources["secret"]),
   226  					},
   227  					DryRunStrategy: common.DryRunNone,
   228  				},
   229  				&taskrunner.WaitTask{
   230  					TaskName: "wait-0",
   231  					Ids: object.ObjMetadataSet{
   232  						testutil.ToIdentifier(t, resources["deployment"]),
   233  						testutil.ToIdentifier(t, resources["secret"]),
   234  					},
   235  					Condition: taskrunner.AllCurrent,
   236  				},
   237  				&task.InvSetTask{
   238  					TaskName:  "inventory-set-0",
   239  					InvClient: &inventory.FakeClient{},
   240  					InvInfo:   invInfo,
   241  					PrevInventory: object.ObjMetadataSet{
   242  						testutil.ToIdentifier(t, resources["deployment"]),
   243  						testutil.ToIdentifier(t, resources["secret"]),
   244  					},
   245  				},
   246  			},
   247  			expectedStatus: []actuation.ObjectStatus{
   248  				{
   249  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   250  						testutil.ToIdentifier(t, resources["deployment"]),
   251  					),
   252  					Strategy:  actuation.ActuationStrategyApply,
   253  					Actuation: actuation.ActuationPending,
   254  					Reconcile: actuation.ReconcilePending,
   255  				},
   256  				{
   257  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   258  						testutil.ToIdentifier(t, resources["secret"]),
   259  					),
   260  					Strategy:  actuation.ActuationStrategyApply,
   261  					Actuation: actuation.ActuationPending,
   262  					Reconcile: actuation.ReconcilePending,
   263  				},
   264  			},
   265  		},
   266  		"multiple resources with reconcile timeout": {
   267  			applyObjs: []*unstructured.Unstructured{
   268  				testutil.Unstructured(t, resources["deployment"]),
   269  				testutil.Unstructured(t, resources["secret"]),
   270  			},
   271  			options: Options{
   272  				ReconcileTimeout: 1 * time.Minute,
   273  			},
   274  			expectedTasks: []taskrunner.Task{
   275  				&task.InvAddTask{
   276  					TaskName:  "inventory-add-0",
   277  					InvClient: &inventory.FakeClient{},
   278  					InvInfo:   invInfo,
   279  					Objects: object.UnstructuredSet{
   280  						testutil.Unstructured(t, resources["secret"]),
   281  						testutil.Unstructured(t, resources["deployment"]),
   282  					},
   283  				},
   284  				&task.ApplyTask{
   285  					TaskName: "apply-0",
   286  					Objects: []*unstructured.Unstructured{
   287  						testutil.Unstructured(t, resources["secret"]),
   288  						testutil.Unstructured(t, resources["deployment"]),
   289  					},
   290  					DryRunStrategy: common.DryRunNone,
   291  				},
   292  				&taskrunner.WaitTask{
   293  					TaskName: "wait-0",
   294  					Ids: object.ObjMetadataSet{
   295  						testutil.ToIdentifier(t, resources["secret"]),
   296  						testutil.ToIdentifier(t, resources["deployment"]),
   297  					},
   298  					Condition: taskrunner.AllCurrent,
   299  					Timeout:   1 * time.Minute,
   300  				},
   301  				&task.InvSetTask{
   302  					TaskName:  "inventory-set-0",
   303  					InvClient: &inventory.FakeClient{},
   304  					InvInfo:   invInfo,
   305  					PrevInventory: object.ObjMetadataSet{
   306  						testutil.ToIdentifier(t, resources["secret"]),
   307  						testutil.ToIdentifier(t, resources["deployment"]),
   308  					},
   309  				},
   310  			},
   311  			expectedStatus: []actuation.ObjectStatus{
   312  				{
   313  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   314  						testutil.ToIdentifier(t, resources["deployment"]),
   315  					),
   316  					Strategy:  actuation.ActuationStrategyApply,
   317  					Actuation: actuation.ActuationPending,
   318  					Reconcile: actuation.ReconcilePending,
   319  				},
   320  				{
   321  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   322  						testutil.ToIdentifier(t, resources["secret"]),
   323  					),
   324  					Strategy:  actuation.ActuationStrategyApply,
   325  					Actuation: actuation.ActuationPending,
   326  					Reconcile: actuation.ReconcilePending,
   327  				},
   328  			},
   329  		},
   330  		"multiple resources with reconcile timeout and dryrun": {
   331  			applyObjs: []*unstructured.Unstructured{
   332  				testutil.Unstructured(t, resources["deployment"]),
   333  				testutil.Unstructured(t, resources["secret"]),
   334  			},
   335  			options: Options{
   336  				ReconcileTimeout: time.Minute,
   337  				DryRunStrategy:   common.DryRunClient,
   338  			},
   339  			// No wait task, since it is dry run
   340  			expectedTasks: []taskrunner.Task{
   341  				&task.InvAddTask{
   342  					TaskName:  "inventory-add-0",
   343  					InvClient: &inventory.FakeClient{},
   344  					InvInfo:   invInfo,
   345  					Objects: object.UnstructuredSet{
   346  						testutil.Unstructured(t, resources["deployment"]),
   347  						testutil.Unstructured(t, resources["secret"]),
   348  					},
   349  					DryRun: common.DryRunClient,
   350  				},
   351  				&task.ApplyTask{
   352  					TaskName: "apply-0",
   353  					Objects: []*unstructured.Unstructured{
   354  						testutil.Unstructured(t, resources["deployment"]),
   355  						testutil.Unstructured(t, resources["secret"]),
   356  					},
   357  					DryRunStrategy: common.DryRunClient,
   358  				},
   359  				&task.InvSetTask{
   360  					TaskName:  "inventory-set-0",
   361  					InvClient: &inventory.FakeClient{},
   362  					InvInfo:   invInfo,
   363  					PrevInventory: object.ObjMetadataSet{
   364  						testutil.ToIdentifier(t, resources["deployment"]),
   365  						testutil.ToIdentifier(t, resources["secret"]),
   366  					},
   367  					DryRun: common.DryRunClient,
   368  				},
   369  			},
   370  			expectedStatus: []actuation.ObjectStatus{
   371  				{
   372  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   373  						testutil.ToIdentifier(t, resources["deployment"]),
   374  					),
   375  					Strategy:  actuation.ActuationStrategyApply,
   376  					Actuation: actuation.ActuationPending,
   377  					Reconcile: actuation.ReconcilePending,
   378  				},
   379  				{
   380  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   381  						testutil.ToIdentifier(t, resources["secret"]),
   382  					),
   383  					Strategy:  actuation.ActuationStrategyApply,
   384  					Actuation: actuation.ActuationPending,
   385  					Reconcile: actuation.ReconcilePending,
   386  				},
   387  			},
   388  		},
   389  		"multiple resources with reconcile timeout and server-dryrun": {
   390  			applyObjs: []*unstructured.Unstructured{
   391  				testutil.Unstructured(t, resources["pod"]),
   392  				testutil.Unstructured(t, resources["default-pod"]),
   393  			},
   394  			options: Options{
   395  				ReconcileTimeout: time.Minute,
   396  				DryRunStrategy:   common.DryRunServer,
   397  			},
   398  			// No wait task, since it is dry run
   399  			expectedTasks: []taskrunner.Task{
   400  				&task.InvAddTask{
   401  					TaskName:  "inventory-add-0",
   402  					InvClient: &inventory.FakeClient{},
   403  					InvInfo:   invInfo,
   404  					Objects: object.UnstructuredSet{
   405  						testutil.Unstructured(t, resources["pod"]),
   406  						testutil.Unstructured(t, resources["default-pod"]),
   407  					},
   408  					DryRun: common.DryRunServer,
   409  				},
   410  				&task.ApplyTask{
   411  					TaskName: "apply-0",
   412  					Objects: []*unstructured.Unstructured{
   413  						testutil.Unstructured(t, resources["pod"]),
   414  						testutil.Unstructured(t, resources["default-pod"]),
   415  					},
   416  					DryRunStrategy: common.DryRunServer,
   417  				},
   418  				&task.InvSetTask{
   419  					TaskName:  "inventory-set-0",
   420  					InvClient: &inventory.FakeClient{},
   421  					InvInfo:   invInfo,
   422  					PrevInventory: object.ObjMetadataSet{
   423  						testutil.ToIdentifier(t, resources["pod"]),
   424  						testutil.ToIdentifier(t, resources["default-pod"]),
   425  					},
   426  					DryRun: common.DryRunServer,
   427  				},
   428  			},
   429  			expectedStatus: []actuation.ObjectStatus{
   430  				{
   431  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   432  						testutil.ToIdentifier(t, resources["pod"]),
   433  					),
   434  					Strategy:  actuation.ActuationStrategyApply,
   435  					Actuation: actuation.ActuationPending,
   436  					Reconcile: actuation.ReconcilePending,
   437  				},
   438  				{
   439  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   440  						testutil.ToIdentifier(t, resources["default-pod"]),
   441  					),
   442  					Strategy:  actuation.ActuationStrategyApply,
   443  					Actuation: actuation.ActuationPending,
   444  					Reconcile: actuation.ReconcilePending,
   445  				},
   446  			},
   447  		},
   448  		"multiple resources including CRD": {
   449  			applyObjs: []*unstructured.Unstructured{
   450  				testutil.Unstructured(t, resources["crontab1"]),
   451  				testutil.Unstructured(t, resources["crd"]),
   452  				testutil.Unstructured(t, resources["crontab2"]),
   453  			},
   454  			expectedTasks: []taskrunner.Task{
   455  				&task.InvAddTask{
   456  					TaskName:  "inventory-add-0",
   457  					InvClient: &inventory.FakeClient{},
   458  					InvInfo:   invInfo,
   459  					Objects: object.UnstructuredSet{
   460  						testutil.Unstructured(t, resources["crontab1"]),
   461  						testutil.Unstructured(t, resources["crd"]),
   462  						testutil.Unstructured(t, resources["crontab2"]),
   463  					},
   464  				},
   465  				&task.ApplyTask{
   466  					TaskName: "apply-0",
   467  					Objects: []*unstructured.Unstructured{
   468  						testutil.Unstructured(t, resources["crd"]),
   469  					},
   470  					DryRunStrategy: common.DryRunNone,
   471  				},
   472  				&taskrunner.WaitTask{
   473  					TaskName: "wait-0",
   474  					Ids: object.ObjMetadataSet{
   475  						testutil.ToIdentifier(t, resources["crd"]),
   476  					},
   477  					Condition: taskrunner.AllCurrent,
   478  				},
   479  				&task.ApplyTask{
   480  					TaskName: "apply-1",
   481  					Objects: []*unstructured.Unstructured{
   482  						testutil.Unstructured(t, resources["crontab1"]),
   483  						testutil.Unstructured(t, resources["crontab2"]),
   484  					},
   485  					DryRunStrategy: common.DryRunNone,
   486  				},
   487  				&taskrunner.WaitTask{
   488  					TaskName: "wait-1",
   489  					Ids: object.ObjMetadataSet{
   490  						testutil.ToIdentifier(t, resources["crontab1"]),
   491  						testutil.ToIdentifier(t, resources["crontab2"]),
   492  					},
   493  					Condition: taskrunner.AllCurrent,
   494  				},
   495  				&task.InvSetTask{
   496  					TaskName:  "inventory-set-0",
   497  					InvClient: &inventory.FakeClient{},
   498  					InvInfo:   invInfo,
   499  					PrevInventory: object.ObjMetadataSet{
   500  						testutil.ToIdentifier(t, resources["crontab1"]),
   501  						testutil.ToIdentifier(t, resources["crd"]),
   502  						testutil.ToIdentifier(t, resources["crontab2"]),
   503  					},
   504  				},
   505  			},
   506  			expectedStatus: []actuation.ObjectStatus{
   507  				{
   508  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   509  						testutil.ToIdentifier(t, resources["crontab1"]),
   510  					),
   511  					Strategy:  actuation.ActuationStrategyApply,
   512  					Actuation: actuation.ActuationPending,
   513  					Reconcile: actuation.ReconcilePending,
   514  				},
   515  				{
   516  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   517  						testutil.ToIdentifier(t, resources["crd"]),
   518  					),
   519  					Strategy:  actuation.ActuationStrategyApply,
   520  					Actuation: actuation.ActuationPending,
   521  					Reconcile: actuation.ReconcilePending,
   522  				},
   523  				{
   524  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   525  						testutil.ToIdentifier(t, resources["crontab2"]),
   526  					),
   527  					Strategy:  actuation.ActuationStrategyApply,
   528  					Actuation: actuation.ActuationPending,
   529  					Reconcile: actuation.ReconcilePending,
   530  				},
   531  			},
   532  		},
   533  		"no wait with CRDs if it is a dryrun": {
   534  			applyObjs: []*unstructured.Unstructured{
   535  				testutil.Unstructured(t, resources["crontab1"]),
   536  				testutil.Unstructured(t, resources["crd"]),
   537  				testutil.Unstructured(t, resources["crontab2"]),
   538  			},
   539  			options: Options{
   540  				ReconcileTimeout: time.Minute,
   541  				DryRunStrategy:   common.DryRunClient,
   542  			},
   543  			expectedTasks: []taskrunner.Task{
   544  				&task.InvAddTask{
   545  					TaskName:  "inventory-add-0",
   546  					InvClient: &inventory.FakeClient{},
   547  					InvInfo:   invInfo,
   548  					Objects: object.UnstructuredSet{
   549  						testutil.Unstructured(t, resources["crontab1"]),
   550  						testutil.Unstructured(t, resources["crd"]),
   551  						testutil.Unstructured(t, resources["crontab2"]),
   552  					},
   553  					DryRun: common.DryRunClient,
   554  				},
   555  				&task.ApplyTask{
   556  					TaskName: "apply-0",
   557  					Objects: []*unstructured.Unstructured{
   558  						testutil.Unstructured(t, resources["crd"]),
   559  					},
   560  					DryRunStrategy: common.DryRunClient,
   561  				},
   562  				&task.ApplyTask{
   563  					TaskName: "apply-1",
   564  					Objects: []*unstructured.Unstructured{
   565  						testutil.Unstructured(t, resources["crontab1"]),
   566  						testutil.Unstructured(t, resources["crontab2"]),
   567  					},
   568  					DryRunStrategy: common.DryRunClient,
   569  				},
   570  				&task.InvSetTask{
   571  					TaskName:  "inventory-set-0",
   572  					InvClient: &inventory.FakeClient{},
   573  					InvInfo:   invInfo,
   574  					PrevInventory: object.ObjMetadataSet{
   575  						testutil.ToIdentifier(t, resources["crontab1"]),
   576  						testutil.ToIdentifier(t, resources["crd"]),
   577  						testutil.ToIdentifier(t, resources["crontab2"]),
   578  					},
   579  					DryRun: common.DryRunClient,
   580  				},
   581  			},
   582  			expectedStatus: []actuation.ObjectStatus{
   583  				{
   584  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   585  						testutil.ToIdentifier(t, resources["crontab1"]),
   586  					),
   587  					Strategy:  actuation.ActuationStrategyApply,
   588  					Actuation: actuation.ActuationPending,
   589  					Reconcile: actuation.ReconcilePending,
   590  				},
   591  				{
   592  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   593  						testutil.ToIdentifier(t, resources["crd"]),
   594  					),
   595  					Strategy:  actuation.ActuationStrategyApply,
   596  					Actuation: actuation.ActuationPending,
   597  					Reconcile: actuation.ReconcilePending,
   598  				},
   599  				{
   600  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   601  						testutil.ToIdentifier(t, resources["crontab2"]),
   602  					),
   603  					Strategy:  actuation.ActuationStrategyApply,
   604  					Actuation: actuation.ActuationPending,
   605  					Reconcile: actuation.ReconcilePending,
   606  				},
   607  			},
   608  		},
   609  		"resources in namespace creates multiple apply tasks": {
   610  			applyObjs: []*unstructured.Unstructured{
   611  				testutil.Unstructured(t, resources["namespace"]),
   612  				testutil.Unstructured(t, resources["pod"]),
   613  				testutil.Unstructured(t, resources["secret"]),
   614  			},
   615  			expectedTasks: []taskrunner.Task{
   616  				&task.InvAddTask{
   617  					TaskName:  "inventory-add-0",
   618  					InvClient: &inventory.FakeClient{},
   619  					InvInfo:   invInfo,
   620  					Objects: object.UnstructuredSet{
   621  						testutil.Unstructured(t, resources["namespace"]),
   622  						testutil.Unstructured(t, resources["pod"]),
   623  						testutil.Unstructured(t, resources["secret"]),
   624  					},
   625  				},
   626  				&task.ApplyTask{
   627  					TaskName: "apply-0",
   628  					Objects: []*unstructured.Unstructured{
   629  						testutil.Unstructured(t, resources["namespace"]),
   630  					},
   631  					DryRunStrategy: common.DryRunNone,
   632  				},
   633  				&taskrunner.WaitTask{
   634  					TaskName: "wait-0",
   635  					Ids: object.ObjMetadataSet{
   636  						testutil.ToIdentifier(t, resources["namespace"]),
   637  					},
   638  					Condition: taskrunner.AllCurrent,
   639  				},
   640  				&task.ApplyTask{
   641  					TaskName: "apply-1",
   642  					Objects: []*unstructured.Unstructured{
   643  						testutil.Unstructured(t, resources["secret"]),
   644  						testutil.Unstructured(t, resources["pod"]),
   645  					},
   646  					DryRunStrategy: common.DryRunNone,
   647  				},
   648  				&taskrunner.WaitTask{
   649  					TaskName: "wait-1",
   650  					Ids: object.ObjMetadataSet{
   651  						testutil.ToIdentifier(t, resources["secret"]),
   652  						testutil.ToIdentifier(t, resources["pod"]),
   653  					},
   654  					Condition: taskrunner.AllCurrent,
   655  				},
   656  				&task.InvSetTask{
   657  					TaskName:  "inventory-set-0",
   658  					InvClient: &inventory.FakeClient{},
   659  					InvInfo:   invInfo,
   660  					PrevInventory: object.ObjMetadataSet{
   661  						testutil.ToIdentifier(t, resources["namespace"]),
   662  						testutil.ToIdentifier(t, resources["pod"]),
   663  						testutil.ToIdentifier(t, resources["secret"]),
   664  					},
   665  				},
   666  			},
   667  			expectedStatus: []actuation.ObjectStatus{
   668  				{
   669  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   670  						testutil.ToIdentifier(t, resources["namespace"]),
   671  					),
   672  					Strategy:  actuation.ActuationStrategyApply,
   673  					Actuation: actuation.ActuationPending,
   674  					Reconcile: actuation.ReconcilePending,
   675  				},
   676  				{
   677  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   678  						testutil.ToIdentifier(t, resources["pod"]),
   679  					),
   680  					Strategy:  actuation.ActuationStrategyApply,
   681  					Actuation: actuation.ActuationPending,
   682  					Reconcile: actuation.ReconcilePending,
   683  				},
   684  				{
   685  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   686  						testutil.ToIdentifier(t, resources["secret"]),
   687  					),
   688  					Strategy:  actuation.ActuationStrategyApply,
   689  					Actuation: actuation.ActuationPending,
   690  					Reconcile: actuation.ReconcilePending,
   691  				},
   692  			},
   693  		},
   694  		"deployment depends on secret creates multiple tasks": {
   695  			applyObjs: []*unstructured.Unstructured{
   696  				testutil.Unstructured(t, resources["deployment"],
   697  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
   698  				testutil.Unstructured(t, resources["secret"]),
   699  			},
   700  			expectedTasks: []taskrunner.Task{
   701  				&task.InvAddTask{
   702  					TaskName:  "inventory-add-0",
   703  					InvClient: &inventory.FakeClient{},
   704  					InvInfo:   invInfo,
   705  					Objects: object.UnstructuredSet{
   706  						testutil.Unstructured(t, resources["deployment"],
   707  							testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
   708  						testutil.Unstructured(t, resources["secret"]),
   709  					},
   710  				},
   711  				&task.ApplyTask{
   712  					TaskName: "apply-0",
   713  					Objects: []*unstructured.Unstructured{
   714  						testutil.Unstructured(t, resources["secret"]),
   715  					},
   716  					DryRunStrategy: common.DryRunNone,
   717  				},
   718  				&taskrunner.WaitTask{
   719  					TaskName: "wait-0",
   720  					Ids: object.ObjMetadataSet{
   721  						testutil.ToIdentifier(t, resources["secret"]),
   722  					},
   723  					Condition: taskrunner.AllCurrent,
   724  				},
   725  				&task.ApplyTask{
   726  					TaskName: "apply-1",
   727  					Objects: []*unstructured.Unstructured{
   728  						testutil.Unstructured(t, resources["deployment"],
   729  							testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
   730  					},
   731  					DryRunStrategy: common.DryRunNone,
   732  				},
   733  				&taskrunner.WaitTask{
   734  					TaskName: "wait-1",
   735  					Ids: object.ObjMetadataSet{
   736  						testutil.ToIdentifier(t, resources["deployment"]),
   737  					},
   738  					Condition: taskrunner.AllCurrent,
   739  				},
   740  				&task.InvSetTask{
   741  					TaskName:  "inventory-set-0",
   742  					InvClient: &inventory.FakeClient{},
   743  					InvInfo:   invInfo,
   744  					PrevInventory: object.ObjMetadataSet{
   745  						testutil.ToIdentifier(t, resources["deployment"]),
   746  						testutil.ToIdentifier(t, resources["secret"]),
   747  					},
   748  				},
   749  			},
   750  			expectedStatus: []actuation.ObjectStatus{
   751  				{
   752  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   753  						testutil.ToIdentifier(t, resources["deployment"]),
   754  					),
   755  					Strategy:  actuation.ActuationStrategyApply,
   756  					Actuation: actuation.ActuationPending,
   757  					Reconcile: actuation.ReconcilePending,
   758  				},
   759  				{
   760  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   761  						testutil.ToIdentifier(t, resources["secret"]),
   762  					),
   763  					Strategy:  actuation.ActuationStrategyApply,
   764  					Actuation: actuation.ActuationPending,
   765  					Reconcile: actuation.ReconcilePending,
   766  				},
   767  			},
   768  		},
   769  		"cyclic dependency returns error": {
   770  			applyObjs: []*unstructured.Unstructured{
   771  				testutil.Unstructured(t, resources["deployment"],
   772  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
   773  				testutil.Unstructured(t, resources["secret"],
   774  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
   775  			},
   776  			expectedTasks: []taskrunner.Task{},
   777  			expectedError: validation.NewError(
   778  				graph.CyclicDependencyError{
   779  					Edges: []graph.Edge{
   780  						{
   781  							From: testutil.ToIdentifier(t, resources["secret"]),
   782  							To:   testutil.ToIdentifier(t, resources["deployment"]),
   783  						},
   784  						{
   785  							From: testutil.ToIdentifier(t, resources["deployment"]),
   786  							To:   testutil.ToIdentifier(t, resources["secret"]),
   787  						},
   788  					},
   789  				},
   790  				testutil.ToIdentifier(t, resources["secret"]),
   791  				testutil.ToIdentifier(t, resources["deployment"]),
   792  			),
   793  		},
   794  	}
   795  
   796  	for tn, tc := range testCases {
   797  		t.Run(tn, func(t *testing.T) {
   798  			mapper := testutil.NewFakeRESTMapper()
   799  			// inject mapper for equality comparison
   800  			for _, t := range tc.expectedTasks {
   801  				switch typedTask := t.(type) {
   802  				case *task.ApplyTask:
   803  					typedTask.Mapper = mapper
   804  				case *taskrunner.WaitTask:
   805  					typedTask.Mapper = mapper
   806  				}
   807  			}
   808  
   809  			applyIds := object.UnstructuredSetToObjMetadataSet(tc.applyObjs)
   810  			fakeInvClient := inventory.NewFakeClient(applyIds)
   811  			vCollector := &validation.Collector{}
   812  			tqb := TaskQueueBuilder{
   813  				Pruner:    pruner,
   814  				Mapper:    mapper,
   815  				InvClient: fakeInvClient,
   816  				Collector: vCollector,
   817  			}
   818  			taskContext := taskrunner.NewTaskContext(nil, nil)
   819  			tq := tqb.WithInventory(invInfo).
   820  				WithApplyObjects(tc.applyObjs).
   821  				Build(taskContext, tc.options)
   822  			err := vCollector.ToError()
   823  			if tc.expectedError != nil {
   824  				assert.EqualError(t, err, tc.expectedError.Error())
   825  				return
   826  			}
   827  			assert.NoError(t, err)
   828  			asserter.Equal(t, tc.expectedTasks, tq.tasks)
   829  
   830  			actualStatus := taskContext.InventoryManager().Inventory().Status.Objects
   831  			testutil.AssertEqual(t, tc.expectedStatus, actualStatus)
   832  		})
   833  	}
   834  }
   835  
   836  func TestTaskQueueBuilder_PruneBuild(t *testing.T) {
   837  	// Use a custom Asserter to customize the comparison options
   838  	asserter := testutil.NewAsserter(
   839  		cmpopts.EquateErrors(),
   840  		waitTaskComparer(),
   841  		fakeClientComparer(),
   842  		inventoryInfoComparer(),
   843  	)
   844  
   845  	invInfo := inventory.WrapInventoryInfoObj(newInvObject(
   846  		"abc-123", "default", "test"))
   847  
   848  	testCases := map[string]struct {
   849  		pruneObjs      []*unstructured.Unstructured
   850  		options        Options
   851  		expectedTasks  []taskrunner.Task
   852  		expectedError  error
   853  		expectedStatus []actuation.ObjectStatus
   854  	}{
   855  		"no resources, no apply or prune tasks": {
   856  			pruneObjs: []*unstructured.Unstructured{},
   857  			options:   Options{Prune: true},
   858  			expectedTasks: []taskrunner.Task{
   859  				&task.InvAddTask{
   860  					TaskName:  "inventory-add-0",
   861  					InvClient: &inventory.FakeClient{},
   862  					InvInfo:   invInfo,
   863  					Objects:   object.UnstructuredSet{},
   864  				},
   865  				&task.InvSetTask{
   866  					TaskName:      "inventory-set-0",
   867  					InvClient:     &inventory.FakeClient{},
   868  					InvInfo:       invInfo,
   869  					PrevInventory: object.ObjMetadataSet{},
   870  				},
   871  			},
   872  		},
   873  		"single resource, one prune task, one wait task": {
   874  			pruneObjs: []*unstructured.Unstructured{
   875  				testutil.Unstructured(t, resources["default-pod"]),
   876  			},
   877  			options: Options{Prune: true},
   878  			expectedTasks: []taskrunner.Task{
   879  				&task.InvAddTask{
   880  					TaskName:  "inventory-add-0",
   881  					InvClient: &inventory.FakeClient{},
   882  					InvInfo:   invInfo,
   883  					Objects:   object.UnstructuredSet{},
   884  				},
   885  				&task.PruneTask{
   886  					TaskName: "prune-0",
   887  					Objects: []*unstructured.Unstructured{
   888  						testutil.Unstructured(t, resources["default-pod"]),
   889  					},
   890  				},
   891  				&taskrunner.WaitTask{
   892  					TaskName: "wait-0",
   893  					Ids: object.ObjMetadataSet{
   894  						testutil.ToIdentifier(t, resources["default-pod"]),
   895  					},
   896  					Condition: taskrunner.AllNotFound,
   897  				},
   898  				&task.InvSetTask{
   899  					TaskName:  "inventory-set-0",
   900  					InvClient: &inventory.FakeClient{},
   901  					InvInfo:   invInfo,
   902  					PrevInventory: object.ObjMetadataSet{
   903  						testutil.ToIdentifier(t, resources["default-pod"]),
   904  					},
   905  				},
   906  			},
   907  			expectedStatus: []actuation.ObjectStatus{
   908  				{
   909  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   910  						testutil.ToIdentifier(t, resources["default-pod"]),
   911  					),
   912  					Strategy:  actuation.ActuationStrategyDelete,
   913  					Actuation: actuation.ActuationPending,
   914  					Reconcile: actuation.ReconcilePending,
   915  				},
   916  			},
   917  		},
   918  		"multiple resources, one prune task, one wait task": {
   919  			pruneObjs: []*unstructured.Unstructured{
   920  				testutil.Unstructured(t, resources["default-pod"]),
   921  				testutil.Unstructured(t, resources["pod"]),
   922  			},
   923  			options: Options{Prune: true},
   924  			expectedTasks: []taskrunner.Task{
   925  				&task.InvAddTask{
   926  					TaskName:  "inventory-add-0",
   927  					InvClient: &inventory.FakeClient{},
   928  					InvInfo:   invInfo,
   929  					Objects:   object.UnstructuredSet{},
   930  				},
   931  				&task.PruneTask{
   932  					TaskName: "prune-0",
   933  					Objects: []*unstructured.Unstructured{
   934  						testutil.Unstructured(t, resources["default-pod"]),
   935  						testutil.Unstructured(t, resources["pod"]),
   936  					},
   937  				},
   938  				&taskrunner.WaitTask{
   939  					TaskName: "wait-0",
   940  					Ids: object.ObjMetadataSet{
   941  						testutil.ToIdentifier(t, resources["default-pod"]),
   942  						testutil.ToIdentifier(t, resources["pod"]),
   943  					},
   944  					Condition: taskrunner.AllNotFound,
   945  				},
   946  				&task.InvSetTask{
   947  					TaskName:  "inventory-set-0",
   948  					InvClient: &inventory.FakeClient{},
   949  					InvInfo:   invInfo,
   950  					PrevInventory: object.ObjMetadataSet{
   951  						testutil.ToIdentifier(t, resources["default-pod"]),
   952  						testutil.ToIdentifier(t, resources["pod"]),
   953  					},
   954  				},
   955  			},
   956  			expectedStatus: []actuation.ObjectStatus{
   957  				{
   958  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   959  						testutil.ToIdentifier(t, resources["default-pod"]),
   960  					),
   961  					Strategy:  actuation.ActuationStrategyDelete,
   962  					Actuation: actuation.ActuationPending,
   963  					Reconcile: actuation.ReconcilePending,
   964  				},
   965  				{
   966  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
   967  						testutil.ToIdentifier(t, resources["pod"]),
   968  					),
   969  					Strategy:  actuation.ActuationStrategyDelete,
   970  					Actuation: actuation.ActuationPending,
   971  					Reconcile: actuation.ReconcilePending,
   972  				},
   973  			},
   974  		},
   975  		"dependent resources, two prune tasks, two wait tasks": {
   976  			pruneObjs: []*unstructured.Unstructured{
   977  				testutil.Unstructured(t, resources["pod"],
   978  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
   979  				testutil.Unstructured(t, resources["secret"]),
   980  			},
   981  			options: Options{Prune: true},
   982  			// Opposite ordering when pruning/deleting
   983  			expectedTasks: []taskrunner.Task{
   984  				&task.InvAddTask{
   985  					TaskName:  "inventory-add-0",
   986  					InvClient: &inventory.FakeClient{},
   987  					InvInfo:   invInfo,
   988  					Objects:   object.UnstructuredSet{},
   989  				},
   990  				&task.PruneTask{
   991  					TaskName: "prune-0",
   992  					Objects: []*unstructured.Unstructured{
   993  						testutil.Unstructured(t, resources["pod"],
   994  							testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
   995  					},
   996  				},
   997  				&taskrunner.WaitTask{
   998  					TaskName: "wait-0",
   999  					Ids: object.ObjMetadataSet{
  1000  						testutil.ToIdentifier(t, resources["pod"]),
  1001  					},
  1002  					Condition: taskrunner.AllNotFound,
  1003  				},
  1004  				&task.PruneTask{
  1005  					TaskName: "prune-1",
  1006  					Objects: []*unstructured.Unstructured{
  1007  						testutil.Unstructured(t, resources["secret"]),
  1008  					},
  1009  				},
  1010  				&taskrunner.WaitTask{
  1011  					TaskName: "wait-1",
  1012  					Ids: object.ObjMetadataSet{
  1013  						testutil.ToIdentifier(t, resources["secret"]),
  1014  					},
  1015  					Condition: taskrunner.AllNotFound,
  1016  				},
  1017  				&task.InvSetTask{
  1018  					TaskName:  "inventory-set-0",
  1019  					InvClient: &inventory.FakeClient{},
  1020  					InvInfo:   invInfo,
  1021  					PrevInventory: object.ObjMetadataSet{
  1022  						testutil.ToIdentifier(t, resources["pod"]),
  1023  						testutil.ToIdentifier(t, resources["secret"]),
  1024  					},
  1025  				},
  1026  			},
  1027  			expectedStatus: []actuation.ObjectStatus{
  1028  				{
  1029  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1030  						testutil.ToIdentifier(t, resources["pod"]),
  1031  					),
  1032  					Strategy:  actuation.ActuationStrategyDelete,
  1033  					Actuation: actuation.ActuationPending,
  1034  					Reconcile: actuation.ReconcilePending,
  1035  				},
  1036  				{
  1037  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1038  						testutil.ToIdentifier(t, resources["secret"]),
  1039  					),
  1040  					Strategy:  actuation.ActuationStrategyDelete,
  1041  					Actuation: actuation.ActuationPending,
  1042  					Reconcile: actuation.ReconcilePending,
  1043  				},
  1044  			},
  1045  		},
  1046  		"single resource with prune timeout has wait task": {
  1047  			pruneObjs: []*unstructured.Unstructured{
  1048  				testutil.Unstructured(t, resources["pod"]),
  1049  			},
  1050  			options: Options{
  1051  				Prune:        true,
  1052  				PruneTimeout: 3 * time.Minute,
  1053  			},
  1054  			expectedTasks: []taskrunner.Task{
  1055  				&task.InvAddTask{
  1056  					TaskName:  "inventory-add-0",
  1057  					InvClient: &inventory.FakeClient{},
  1058  					InvInfo:   invInfo,
  1059  					Objects:   object.UnstructuredSet{},
  1060  				},
  1061  				&task.PruneTask{
  1062  					TaskName: "prune-0",
  1063  					Objects: []*unstructured.Unstructured{
  1064  						testutil.Unstructured(t, resources["pod"]),
  1065  					},
  1066  				},
  1067  				&taskrunner.WaitTask{
  1068  					TaskName: "wait-0",
  1069  					Ids: object.ObjMetadataSet{
  1070  						testutil.ToIdentifier(t, resources["pod"]),
  1071  					},
  1072  					Condition: taskrunner.AllNotFound,
  1073  					Timeout:   3 * time.Minute,
  1074  				},
  1075  				&task.InvSetTask{
  1076  					TaskName:  "inventory-set-0",
  1077  					InvClient: &inventory.FakeClient{},
  1078  					InvInfo:   invInfo,
  1079  					PrevInventory: object.ObjMetadataSet{
  1080  						testutil.ToIdentifier(t, resources["pod"]),
  1081  					},
  1082  				},
  1083  			},
  1084  			expectedStatus: []actuation.ObjectStatus{
  1085  				{
  1086  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1087  						testutil.ToIdentifier(t, resources["pod"]),
  1088  					),
  1089  					Strategy:  actuation.ActuationStrategyDelete,
  1090  					Actuation: actuation.ActuationPending,
  1091  					Reconcile: actuation.ReconcilePending,
  1092  				},
  1093  			},
  1094  		},
  1095  		"multiple resources with prune timeout and server-dryrun": {
  1096  			pruneObjs: []*unstructured.Unstructured{
  1097  				testutil.Unstructured(t, resources["pod"]),
  1098  				testutil.Unstructured(t, resources["default-pod"]),
  1099  			},
  1100  			options: Options{
  1101  				PruneTimeout:   time.Minute,
  1102  				DryRunStrategy: common.DryRunServer,
  1103  				Prune:          true,
  1104  			},
  1105  			// No wait task, since it is dry run
  1106  			expectedTasks: []taskrunner.Task{
  1107  				&task.InvAddTask{
  1108  					TaskName:  "inventory-add-0",
  1109  					InvClient: &inventory.FakeClient{},
  1110  					InvInfo:   invInfo,
  1111  					Objects:   object.UnstructuredSet{},
  1112  					DryRun:    common.DryRunServer,
  1113  				},
  1114  				&task.PruneTask{
  1115  					TaskName: "prune-0",
  1116  					Objects: []*unstructured.Unstructured{
  1117  						testutil.Unstructured(t, resources["pod"]),
  1118  						testutil.Unstructured(t, resources["default-pod"]),
  1119  					},
  1120  					DryRunStrategy: common.DryRunServer,
  1121  				},
  1122  				&task.InvSetTask{
  1123  					TaskName:  "inventory-set-0",
  1124  					InvClient: &inventory.FakeClient{},
  1125  					InvInfo:   invInfo,
  1126  					PrevInventory: object.ObjMetadataSet{
  1127  						testutil.ToIdentifier(t, resources["pod"]),
  1128  						testutil.ToIdentifier(t, resources["default-pod"]),
  1129  					},
  1130  					DryRun: common.DryRunServer,
  1131  				},
  1132  			},
  1133  			expectedStatus: []actuation.ObjectStatus{
  1134  				{
  1135  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1136  						testutil.ToIdentifier(t, resources["pod"]),
  1137  					),
  1138  					Strategy:  actuation.ActuationStrategyDelete,
  1139  					Actuation: actuation.ActuationPending,
  1140  					Reconcile: actuation.ReconcilePending,
  1141  				},
  1142  				{
  1143  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1144  						testutil.ToIdentifier(t, resources["default-pod"]),
  1145  					),
  1146  					Strategy:  actuation.ActuationStrategyDelete,
  1147  					Actuation: actuation.ActuationPending,
  1148  					Reconcile: actuation.ReconcilePending,
  1149  				},
  1150  			},
  1151  		},
  1152  		"multiple resources including CRD": {
  1153  			pruneObjs: []*unstructured.Unstructured{
  1154  				testutil.Unstructured(t, resources["crontab1"]),
  1155  				testutil.Unstructured(t, resources["crd"]),
  1156  				testutil.Unstructured(t, resources["crontab2"]),
  1157  			},
  1158  			options: Options{Prune: true},
  1159  			// Opposite ordering when pruning/deleting.
  1160  			expectedTasks: []taskrunner.Task{
  1161  				&task.InvAddTask{
  1162  					TaskName:  "inventory-add-0",
  1163  					InvClient: &inventory.FakeClient{},
  1164  					InvInfo:   invInfo,
  1165  					Objects:   object.UnstructuredSet{},
  1166  				},
  1167  				&task.PruneTask{
  1168  					TaskName: "prune-0",
  1169  					Objects: []*unstructured.Unstructured{
  1170  						testutil.Unstructured(t, resources["crontab1"]),
  1171  						testutil.Unstructured(t, resources["crontab2"]),
  1172  					},
  1173  				},
  1174  				&taskrunner.WaitTask{
  1175  					TaskName: "wait-0",
  1176  					Ids: object.ObjMetadataSet{
  1177  						testutil.ToIdentifier(t, resources["crontab1"]),
  1178  						testutil.ToIdentifier(t, resources["crontab2"]),
  1179  					},
  1180  					Condition: taskrunner.AllNotFound,
  1181  				},
  1182  				&task.PruneTask{
  1183  					TaskName: "prune-1",
  1184  					Objects: []*unstructured.Unstructured{
  1185  						testutil.Unstructured(t, resources["crd"]),
  1186  					},
  1187  				},
  1188  				&taskrunner.WaitTask{
  1189  					TaskName: "wait-1",
  1190  					Ids: object.ObjMetadataSet{
  1191  						testutil.ToIdentifier(t, resources["crd"]),
  1192  					},
  1193  					Condition: taskrunner.AllNotFound,
  1194  				},
  1195  				&task.InvSetTask{
  1196  					TaskName:  "inventory-set-0",
  1197  					InvClient: &inventory.FakeClient{},
  1198  					InvInfo:   invInfo,
  1199  					PrevInventory: object.ObjMetadataSet{
  1200  						testutil.ToIdentifier(t, resources["crontab1"]),
  1201  						testutil.ToIdentifier(t, resources["crd"]),
  1202  						testutil.ToIdentifier(t, resources["crontab2"]),
  1203  					},
  1204  				},
  1205  			},
  1206  			expectedStatus: []actuation.ObjectStatus{
  1207  				{
  1208  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1209  						testutil.ToIdentifier(t, resources["crontab1"]),
  1210  					),
  1211  					Strategy:  actuation.ActuationStrategyDelete,
  1212  					Actuation: actuation.ActuationPending,
  1213  					Reconcile: actuation.ReconcilePending,
  1214  				},
  1215  				{
  1216  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1217  						testutil.ToIdentifier(t, resources["crd"]),
  1218  					),
  1219  					Strategy:  actuation.ActuationStrategyDelete,
  1220  					Actuation: actuation.ActuationPending,
  1221  					Reconcile: actuation.ReconcilePending,
  1222  				},
  1223  				{
  1224  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1225  						testutil.ToIdentifier(t, resources["crontab2"]),
  1226  					),
  1227  					Strategy:  actuation.ActuationStrategyDelete,
  1228  					Actuation: actuation.ActuationPending,
  1229  					Reconcile: actuation.ReconcilePending,
  1230  				},
  1231  			},
  1232  		},
  1233  		"no wait with CRDs if it is a dryrun": {
  1234  			pruneObjs: []*unstructured.Unstructured{
  1235  				testutil.Unstructured(t, resources["crontab1"]),
  1236  				testutil.Unstructured(t, resources["crd"]),
  1237  				testutil.Unstructured(t, resources["crontab2"]),
  1238  			},
  1239  			options: Options{
  1240  				ReconcileTimeout: time.Minute,
  1241  				DryRunStrategy:   common.DryRunClient,
  1242  				Prune:            true,
  1243  			},
  1244  			expectedTasks: []taskrunner.Task{
  1245  				&task.InvAddTask{
  1246  					TaskName:  "inventory-add-0",
  1247  					InvClient: &inventory.FakeClient{},
  1248  					InvInfo:   invInfo,
  1249  					Objects:   object.UnstructuredSet{},
  1250  					DryRun:    common.DryRunClient,
  1251  				},
  1252  				&task.PruneTask{
  1253  					TaskName: "prune-0",
  1254  					Objects: []*unstructured.Unstructured{
  1255  						testutil.Unstructured(t, resources["crontab1"]),
  1256  						testutil.Unstructured(t, resources["crontab2"]),
  1257  					},
  1258  					DryRunStrategy: common.DryRunClient,
  1259  				},
  1260  				&task.PruneTask{
  1261  					TaskName: "prune-1",
  1262  					Objects: []*unstructured.Unstructured{
  1263  						testutil.Unstructured(t, resources["crd"]),
  1264  					},
  1265  					DryRunStrategy: common.DryRunClient,
  1266  				},
  1267  				&task.InvSetTask{
  1268  					TaskName:  "inventory-set-0",
  1269  					InvClient: &inventory.FakeClient{},
  1270  					InvInfo:   invInfo,
  1271  					PrevInventory: object.ObjMetadataSet{
  1272  						testutil.ToIdentifier(t, resources["crontab1"]),
  1273  						testutil.ToIdentifier(t, resources["crd"]),
  1274  						testutil.ToIdentifier(t, resources["crontab2"]),
  1275  					},
  1276  					DryRun: common.DryRunClient,
  1277  				},
  1278  			},
  1279  			expectedStatus: []actuation.ObjectStatus{
  1280  				{
  1281  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1282  						testutil.ToIdentifier(t, resources["crontab1"]),
  1283  					),
  1284  					Strategy:  actuation.ActuationStrategyDelete,
  1285  					Actuation: actuation.ActuationPending,
  1286  					Reconcile: actuation.ReconcilePending,
  1287  				},
  1288  				{
  1289  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1290  						testutil.ToIdentifier(t, resources["crd"]),
  1291  					),
  1292  					Strategy:  actuation.ActuationStrategyDelete,
  1293  					Actuation: actuation.ActuationPending,
  1294  					Reconcile: actuation.ReconcilePending,
  1295  				},
  1296  				{
  1297  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1298  						testutil.ToIdentifier(t, resources["crontab2"]),
  1299  					),
  1300  					Strategy:  actuation.ActuationStrategyDelete,
  1301  					Actuation: actuation.ActuationPending,
  1302  					Reconcile: actuation.ReconcilePending,
  1303  				},
  1304  			},
  1305  		},
  1306  		"resources in namespace creates multiple apply tasks": {
  1307  			pruneObjs: []*unstructured.Unstructured{
  1308  				testutil.Unstructured(t, resources["namespace"]),
  1309  				testutil.Unstructured(t, resources["pod"]),
  1310  				testutil.Unstructured(t, resources["secret"]),
  1311  			},
  1312  			options: Options{Prune: true},
  1313  			expectedTasks: []taskrunner.Task{
  1314  				&task.InvAddTask{
  1315  					TaskName:  "inventory-add-0",
  1316  					InvClient: &inventory.FakeClient{},
  1317  					InvInfo:   invInfo,
  1318  					Objects:   object.UnstructuredSet{},
  1319  				},
  1320  				&task.PruneTask{
  1321  					TaskName: "prune-0",
  1322  					Objects: []*unstructured.Unstructured{
  1323  						testutil.Unstructured(t, resources["pod"]),
  1324  						testutil.Unstructured(t, resources["secret"]),
  1325  					},
  1326  				},
  1327  				&taskrunner.WaitTask{
  1328  					TaskName: "wait-0",
  1329  					Ids: object.ObjMetadataSet{
  1330  						testutil.ToIdentifier(t, resources["pod"]),
  1331  						testutil.ToIdentifier(t, resources["secret"]),
  1332  					},
  1333  					Condition: taskrunner.AllNotFound,
  1334  				},
  1335  				&task.PruneTask{
  1336  					TaskName: "prune-1",
  1337  					Objects: []*unstructured.Unstructured{
  1338  						testutil.Unstructured(t, resources["namespace"]),
  1339  					},
  1340  				},
  1341  				&taskrunner.WaitTask{
  1342  					TaskName: "wait-1",
  1343  					Ids: object.ObjMetadataSet{
  1344  						testutil.ToIdentifier(t, resources["namespace"]),
  1345  					},
  1346  					Condition: taskrunner.AllNotFound,
  1347  				},
  1348  				&task.InvSetTask{
  1349  					TaskName:  "inventory-set-0",
  1350  					InvClient: &inventory.FakeClient{},
  1351  					InvInfo:   invInfo,
  1352  					PrevInventory: object.ObjMetadataSet{
  1353  						testutil.ToIdentifier(t, resources["namespace"]),
  1354  						testutil.ToIdentifier(t, resources["pod"]),
  1355  						testutil.ToIdentifier(t, resources["secret"]),
  1356  					},
  1357  				},
  1358  			},
  1359  			expectedStatus: []actuation.ObjectStatus{
  1360  				{
  1361  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1362  						testutil.ToIdentifier(t, resources["namespace"]),
  1363  					),
  1364  					Strategy:  actuation.ActuationStrategyDelete,
  1365  					Actuation: actuation.ActuationPending,
  1366  					Reconcile: actuation.ReconcilePending,
  1367  				},
  1368  				{
  1369  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1370  						testutil.ToIdentifier(t, resources["pod"]),
  1371  					),
  1372  					Strategy:  actuation.ActuationStrategyDelete,
  1373  					Actuation: actuation.ActuationPending,
  1374  					Reconcile: actuation.ReconcilePending,
  1375  				},
  1376  				{
  1377  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1378  						testutil.ToIdentifier(t, resources["secret"]),
  1379  					),
  1380  					Strategy:  actuation.ActuationStrategyDelete,
  1381  					Actuation: actuation.ActuationPending,
  1382  					Reconcile: actuation.ReconcilePending,
  1383  				},
  1384  			},
  1385  		},
  1386  		"cyclic dependency": {
  1387  			pruneObjs: []*unstructured.Unstructured{
  1388  				testutil.Unstructured(t, resources["deployment"],
  1389  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
  1390  				testutil.Unstructured(t, resources["secret"],
  1391  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
  1392  			},
  1393  			options:       Options{Prune: true},
  1394  			expectedTasks: []taskrunner.Task{},
  1395  			expectedError: validation.NewError(
  1396  				graph.CyclicDependencyError{
  1397  					Edges: []graph.Edge{
  1398  						{
  1399  							From: testutil.ToIdentifier(t, resources["secret"]),
  1400  							To:   testutil.ToIdentifier(t, resources["deployment"]),
  1401  						},
  1402  						{
  1403  							From: testutil.ToIdentifier(t, resources["deployment"]),
  1404  							To:   testutil.ToIdentifier(t, resources["secret"]),
  1405  						},
  1406  					},
  1407  				},
  1408  				testutil.ToIdentifier(t, resources["secret"]),
  1409  				testutil.ToIdentifier(t, resources["deployment"]),
  1410  			),
  1411  		},
  1412  		"cyclic dependency and valid": {
  1413  			pruneObjs: []*unstructured.Unstructured{
  1414  				testutil.Unstructured(t, resources["deployment"],
  1415  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
  1416  				testutil.Unstructured(t, resources["secret"],
  1417  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
  1418  				testutil.Unstructured(t, resources["pod"]),
  1419  			},
  1420  			options: Options{Prune: true},
  1421  			expectedTasks: []taskrunner.Task{
  1422  				&task.InvAddTask{
  1423  					TaskName:  "inventory-add-0",
  1424  					InvClient: &inventory.FakeClient{},
  1425  					InvInfo:   invInfo,
  1426  					Objects:   object.UnstructuredSet{},
  1427  				},
  1428  				&task.PruneTask{
  1429  					TaskName: "prune-0",
  1430  					Objects: []*unstructured.Unstructured{
  1431  						testutil.Unstructured(t, resources["pod"]),
  1432  					},
  1433  				},
  1434  				taskrunner.NewWaitTask(
  1435  					"wait-0",
  1436  					object.ObjMetadataSet{
  1437  						testutil.ToIdentifier(t, resources["pod"]),
  1438  					},
  1439  					taskrunner.AllCurrent, 1*time.Second,
  1440  					testutil.NewFakeRESTMapper(),
  1441  				),
  1442  				&task.InvSetTask{
  1443  					TaskName:  "inventory-set-0",
  1444  					InvClient: &inventory.FakeClient{},
  1445  					InvInfo:   invInfo,
  1446  					PrevInventory: object.ObjMetadataSet{
  1447  						testutil.ToIdentifier(t, resources["pod"]),
  1448  					},
  1449  				},
  1450  			},
  1451  			expectedError: validation.NewError(
  1452  				graph.CyclicDependencyError{
  1453  					Edges: []graph.Edge{
  1454  						{
  1455  							From: testutil.ToIdentifier(t, resources["secret"]),
  1456  							To:   testutil.ToIdentifier(t, resources["deployment"]),
  1457  						},
  1458  						{
  1459  							From: testutil.ToIdentifier(t, resources["deployment"]),
  1460  							To:   testutil.ToIdentifier(t, resources["secret"]),
  1461  						},
  1462  					},
  1463  				},
  1464  				testutil.ToIdentifier(t, resources["secret"]),
  1465  				testutil.ToIdentifier(t, resources["deployment"]),
  1466  			),
  1467  		},
  1468  	}
  1469  
  1470  	for tn, tc := range testCases {
  1471  		t.Run(tn, func(t *testing.T) {
  1472  			mapper := testutil.NewFakeRESTMapper()
  1473  			// inject mapper & pruner for equality comparison
  1474  			for _, t := range tc.expectedTasks {
  1475  				switch typedTask := t.(type) {
  1476  				case *task.PruneTask:
  1477  					typedTask.Pruner = &prune.Pruner{}
  1478  				case *taskrunner.WaitTask:
  1479  					typedTask.Mapper = mapper
  1480  				}
  1481  			}
  1482  
  1483  			pruneIds := object.UnstructuredSetToObjMetadataSet(tc.pruneObjs)
  1484  			fakeInvClient := inventory.NewFakeClient(pruneIds)
  1485  			vCollector := &validation.Collector{}
  1486  			tqb := TaskQueueBuilder{
  1487  				Pruner:    pruner,
  1488  				Mapper:    mapper,
  1489  				InvClient: fakeInvClient,
  1490  				Collector: vCollector,
  1491  			}
  1492  			taskContext := taskrunner.NewTaskContext(nil, nil)
  1493  			tq := tqb.WithInventory(invInfo).
  1494  				WithPruneObjects(tc.pruneObjs).
  1495  				Build(taskContext, tc.options)
  1496  			err := vCollector.ToError()
  1497  			if tc.expectedError != nil {
  1498  				assert.EqualError(t, err, tc.expectedError.Error())
  1499  				return
  1500  			}
  1501  			assert.NoError(t, err)
  1502  			asserter.Equal(t, tc.expectedTasks, tq.tasks)
  1503  
  1504  			actualStatus := taskContext.InventoryManager().Inventory().Status.Objects
  1505  			testutil.AssertEqual(t, tc.expectedStatus, actualStatus)
  1506  		})
  1507  	}
  1508  }
  1509  
  1510  func TestTaskQueueBuilder_ApplyPruneBuild(t *testing.T) {
  1511  	// Use a custom Asserter to customize the comparison options
  1512  	asserter := testutil.NewAsserter(
  1513  		cmpopts.EquateErrors(),
  1514  		waitTaskComparer(),
  1515  		fakeClientComparer(),
  1516  		inventoryInfoComparer(),
  1517  	)
  1518  
  1519  	invInfo := inventory.WrapInventoryInfoObj(newInvObject(
  1520  		"abc-123", "default", "test"))
  1521  
  1522  	testCases := map[string]struct {
  1523  		inventoryIDs   object.ObjMetadataSet
  1524  		applyObjs      object.UnstructuredSet
  1525  		pruneObjs      object.UnstructuredSet
  1526  		options        Options
  1527  		expectedTasks  []taskrunner.Task
  1528  		expectedError  error
  1529  		expectedStatus []actuation.ObjectStatus
  1530  	}{
  1531  		"two resources, one apply, one prune": {
  1532  			inventoryIDs: object.ObjMetadataSet{
  1533  				testutil.ToIdentifier(t, resources["secret"]),
  1534  			},
  1535  			applyObjs: object.UnstructuredSet{
  1536  				testutil.Unstructured(t, resources["deployment"]),
  1537  			},
  1538  			pruneObjs: object.UnstructuredSet{
  1539  				testutil.Unstructured(t, resources["secret"]),
  1540  			},
  1541  			options: Options{Prune: true},
  1542  			expectedTasks: []taskrunner.Task{
  1543  				&task.InvAddTask{
  1544  					TaskName:  "inventory-add-0",
  1545  					InvClient: &inventory.FakeClient{},
  1546  					InvInfo:   invInfo,
  1547  					Objects: object.UnstructuredSet{
  1548  						testutil.Unstructured(t, resources["deployment"]),
  1549  					},
  1550  				},
  1551  				&task.ApplyTask{
  1552  					TaskName: "apply-0",
  1553  					Objects: []*unstructured.Unstructured{
  1554  						testutil.Unstructured(t, resources["deployment"]),
  1555  					},
  1556  				},
  1557  				&taskrunner.WaitTask{
  1558  					TaskName: "wait-0",
  1559  					Ids: object.ObjMetadataSet{
  1560  						testutil.ToIdentifier(t, resources["deployment"]),
  1561  					},
  1562  					Condition: taskrunner.AllCurrent,
  1563  				},
  1564  				&task.PruneTask{
  1565  					TaskName: "prune-0",
  1566  					Objects: []*unstructured.Unstructured{
  1567  						testutil.Unstructured(t, resources["secret"]),
  1568  					},
  1569  				},
  1570  				&taskrunner.WaitTask{
  1571  					TaskName: "wait-1",
  1572  					Ids: object.ObjMetadataSet{
  1573  						testutil.ToIdentifier(t, resources["secret"]),
  1574  					},
  1575  					Condition: taskrunner.AllNotFound,
  1576  				},
  1577  				&task.InvSetTask{
  1578  					TaskName:  "inventory-set-0",
  1579  					InvClient: &inventory.FakeClient{},
  1580  					InvInfo:   invInfo,
  1581  					PrevInventory: object.ObjMetadataSet{
  1582  						testutil.ToIdentifier(t, resources["secret"]),
  1583  					},
  1584  				},
  1585  			},
  1586  			expectedStatus: []actuation.ObjectStatus{
  1587  				{
  1588  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1589  						testutil.ToIdentifier(t, resources["deployment"]),
  1590  					),
  1591  					Strategy:  actuation.ActuationStrategyApply,
  1592  					Actuation: actuation.ActuationPending,
  1593  					Reconcile: actuation.ReconcilePending,
  1594  				},
  1595  				{
  1596  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1597  						testutil.ToIdentifier(t, resources["secret"]),
  1598  					),
  1599  					Strategy:  actuation.ActuationStrategyDelete,
  1600  					Actuation: actuation.ActuationPending,
  1601  					Reconcile: actuation.ReconcilePending,
  1602  				},
  1603  			},
  1604  		},
  1605  		"prune disabled": {
  1606  			inventoryIDs: object.ObjMetadataSet{
  1607  				testutil.ToIdentifier(t, resources["secret"]),
  1608  			},
  1609  			applyObjs: object.UnstructuredSet{
  1610  				testutil.Unstructured(t, resources["deployment"]),
  1611  			},
  1612  			pruneObjs: object.UnstructuredSet{
  1613  				testutil.Unstructured(t, resources["secret"]),
  1614  			},
  1615  			options: Options{Prune: false},
  1616  			expectedTasks: []taskrunner.Task{
  1617  				&task.InvAddTask{
  1618  					TaskName:  "inventory-add-0",
  1619  					InvClient: &inventory.FakeClient{},
  1620  					InvInfo:   invInfo,
  1621  					Objects: object.UnstructuredSet{
  1622  						testutil.Unstructured(t, resources["deployment"]),
  1623  					},
  1624  				},
  1625  				&task.ApplyTask{
  1626  					TaskName: "apply-0",
  1627  					Objects: []*unstructured.Unstructured{
  1628  						testutil.Unstructured(t, resources["deployment"]),
  1629  					},
  1630  				},
  1631  				&taskrunner.WaitTask{
  1632  					TaskName: "wait-0",
  1633  					Ids: object.ObjMetadataSet{
  1634  						testutil.ToIdentifier(t, resources["deployment"]),
  1635  					},
  1636  					Condition: taskrunner.AllCurrent,
  1637  				},
  1638  				&task.InvSetTask{
  1639  					TaskName:  "inventory-set-0",
  1640  					InvClient: &inventory.FakeClient{},
  1641  					InvInfo:   invInfo,
  1642  					PrevInventory: object.ObjMetadataSet{
  1643  						testutil.ToIdentifier(t, resources["secret"]),
  1644  					},
  1645  				},
  1646  			},
  1647  			expectedStatus: []actuation.ObjectStatus{
  1648  				{
  1649  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1650  						testutil.ToIdentifier(t, resources["deployment"]),
  1651  					),
  1652  					Strategy:  actuation.ActuationStrategyApply,
  1653  					Actuation: actuation.ActuationPending,
  1654  					Reconcile: actuation.ReconcilePending,
  1655  				},
  1656  			},
  1657  		},
  1658  		// This use case returns in a task plan that would cause a dependency
  1659  		// to be deleted. This is remediated by the DependencyFilter at
  1660  		// apply-time, by skipping both the apply and prune.
  1661  		// This test does not verify the DependencyFilter tho, just that the
  1662  		// dependency was discovered between apply & prune objects.
  1663  		"dependency: apply -> prune": {
  1664  			inventoryIDs: object.ObjMetadataSet{
  1665  				testutil.ToIdentifier(t, resources["secret"]),
  1666  			},
  1667  			applyObjs: object.UnstructuredSet{
  1668  				testutil.Unstructured(t, resources["deployment"],
  1669  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
  1670  			},
  1671  			pruneObjs: object.UnstructuredSet{
  1672  				testutil.Unstructured(t, resources["secret"]),
  1673  			},
  1674  			options: Options{Prune: true},
  1675  			expectedTasks: []taskrunner.Task{
  1676  				&task.InvAddTask{
  1677  					TaskName:  "inventory-add-0",
  1678  					InvClient: &inventory.FakeClient{},
  1679  					InvInfo:   invInfo,
  1680  					Objects: object.UnstructuredSet{
  1681  						testutil.Unstructured(t, resources["deployment"],
  1682  							testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
  1683  					},
  1684  				},
  1685  				&task.ApplyTask{
  1686  					TaskName: "apply-0",
  1687  					Objects: []*unstructured.Unstructured{
  1688  						testutil.Unstructured(t, resources["deployment"],
  1689  							testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
  1690  					},
  1691  				},
  1692  				&taskrunner.WaitTask{
  1693  					TaskName: "wait-0",
  1694  					Ids: object.ObjMetadataSet{
  1695  						testutil.ToIdentifier(t, resources["deployment"]),
  1696  					},
  1697  					Condition: taskrunner.AllCurrent,
  1698  				},
  1699  				&task.PruneTask{
  1700  					TaskName: "prune-0",
  1701  					Objects: []*unstructured.Unstructured{
  1702  						testutil.Unstructured(t, resources["secret"]),
  1703  					},
  1704  				},
  1705  				&taskrunner.WaitTask{
  1706  					TaskName: "wait-1",
  1707  					Ids: object.ObjMetadataSet{
  1708  						testutil.ToIdentifier(t, resources["secret"]),
  1709  					},
  1710  					Condition: taskrunner.AllNotFound,
  1711  				},
  1712  				&task.InvSetTask{
  1713  					TaskName:  "inventory-set-0",
  1714  					InvClient: &inventory.FakeClient{},
  1715  					InvInfo:   invInfo,
  1716  					PrevInventory: object.ObjMetadataSet{
  1717  						testutil.ToIdentifier(t, resources["secret"]),
  1718  					},
  1719  				},
  1720  			},
  1721  			expectedStatus: []actuation.ObjectStatus{
  1722  				{
  1723  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1724  						testutil.ToIdentifier(t, resources["deployment"]),
  1725  					),
  1726  					Strategy:  actuation.ActuationStrategyApply,
  1727  					Actuation: actuation.ActuationPending,
  1728  					Reconcile: actuation.ReconcilePending,
  1729  				},
  1730  				{
  1731  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1732  						testutil.ToIdentifier(t, resources["secret"]),
  1733  					),
  1734  					Strategy:  actuation.ActuationStrategyDelete,
  1735  					Actuation: actuation.ActuationPending,
  1736  					Reconcile: actuation.ReconcilePending,
  1737  				},
  1738  			},
  1739  		},
  1740  		// This use case returns in a task plan that would cause a dependency
  1741  		// to be applied. This is fine.
  1742  		// This test just verifies that the  dependency was discovered between
  1743  		// prune & apply objects.
  1744  		"dependency: prune -> apply": {
  1745  			inventoryIDs: object.ObjMetadataSet{
  1746  				testutil.ToIdentifier(t, resources["secret"]),
  1747  			},
  1748  			applyObjs: object.UnstructuredSet{
  1749  				testutil.Unstructured(t, resources["deployment"]),
  1750  			},
  1751  			pruneObjs: object.UnstructuredSet{
  1752  				testutil.Unstructured(t, resources["secret"],
  1753  					testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
  1754  			},
  1755  			options: Options{Prune: true},
  1756  			expectedTasks: []taskrunner.Task{
  1757  				&task.InvAddTask{
  1758  					TaskName:  "inventory-add-0",
  1759  					InvClient: &inventory.FakeClient{},
  1760  					InvInfo:   invInfo,
  1761  					Objects: object.UnstructuredSet{
  1762  						testutil.Unstructured(t, resources["deployment"]),
  1763  					},
  1764  				},
  1765  				&task.ApplyTask{
  1766  					TaskName: "apply-0",
  1767  					Objects: []*unstructured.Unstructured{
  1768  						testutil.Unstructured(t, resources["deployment"]),
  1769  					},
  1770  				},
  1771  				&taskrunner.WaitTask{
  1772  					TaskName: "wait-0",
  1773  					Ids: object.ObjMetadataSet{
  1774  						testutil.ToIdentifier(t, resources["deployment"]),
  1775  					},
  1776  					Condition: taskrunner.AllCurrent,
  1777  				},
  1778  				&task.PruneTask{
  1779  					TaskName: "prune-0",
  1780  					Objects: []*unstructured.Unstructured{
  1781  						testutil.Unstructured(t, resources["secret"],
  1782  							testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
  1783  					},
  1784  				},
  1785  				&taskrunner.WaitTask{
  1786  					TaskName: "wait-1",
  1787  					Ids: object.ObjMetadataSet{
  1788  						testutil.ToIdentifier(t, resources["secret"]),
  1789  					},
  1790  					Condition: taskrunner.AllNotFound,
  1791  				},
  1792  				&task.InvSetTask{
  1793  					TaskName:  "inventory-set-0",
  1794  					InvClient: &inventory.FakeClient{},
  1795  					InvInfo:   invInfo,
  1796  					PrevInventory: object.ObjMetadataSet{
  1797  						testutil.ToIdentifier(t, resources["secret"]),
  1798  					},
  1799  				},
  1800  			},
  1801  			expectedStatus: []actuation.ObjectStatus{
  1802  				{
  1803  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1804  						testutil.ToIdentifier(t, resources["deployment"]),
  1805  					),
  1806  					Strategy:  actuation.ActuationStrategyApply,
  1807  					Actuation: actuation.ActuationPending,
  1808  					Reconcile: actuation.ReconcilePending,
  1809  				},
  1810  				{
  1811  					ObjectReference: inventory.ObjectReferenceFromObjMetadata(
  1812  						testutil.ToIdentifier(t, resources["secret"]),
  1813  					),
  1814  					Strategy:  actuation.ActuationStrategyDelete,
  1815  					Actuation: actuation.ActuationPending,
  1816  					Reconcile: actuation.ReconcilePending,
  1817  				},
  1818  			},
  1819  		},
  1820  	}
  1821  
  1822  	for tn, tc := range testCases {
  1823  		t.Run(tn, func(t *testing.T) {
  1824  			mapper := testutil.NewFakeRESTMapper()
  1825  			// inject mapper & pruner for equality comparison
  1826  			for _, t := range tc.expectedTasks {
  1827  				switch typedTask := t.(type) {
  1828  				case *task.ApplyTask:
  1829  					typedTask.Mapper = mapper
  1830  				case *task.PruneTask:
  1831  					typedTask.Pruner = &prune.Pruner{}
  1832  				case *taskrunner.WaitTask:
  1833  					typedTask.Mapper = mapper
  1834  				}
  1835  			}
  1836  
  1837  			fakeInvClient := inventory.NewFakeClient(tc.inventoryIDs)
  1838  			vCollector := &validation.Collector{}
  1839  			tqb := TaskQueueBuilder{
  1840  				Pruner:    pruner,
  1841  				Mapper:    mapper,
  1842  				InvClient: fakeInvClient,
  1843  				Collector: vCollector,
  1844  			}
  1845  			taskContext := taskrunner.NewTaskContext(nil, nil)
  1846  			tq := tqb.WithInventory(invInfo).
  1847  				WithApplyObjects(tc.applyObjs).
  1848  				WithPruneObjects(tc.pruneObjs).
  1849  				Build(taskContext, tc.options)
  1850  
  1851  			err := vCollector.ToError()
  1852  			if tc.expectedError != nil {
  1853  				assert.EqualError(t, err, tc.expectedError.Error())
  1854  				return
  1855  			}
  1856  			assert.NoError(t, err)
  1857  
  1858  			asserter.Equal(t, tc.expectedTasks, tq.tasks)
  1859  
  1860  			actualStatus := taskContext.InventoryManager().Inventory().Status.Objects
  1861  			testutil.AssertEqual(t, tc.expectedStatus, actualStatus)
  1862  		})
  1863  	}
  1864  }
  1865  
  1866  // waitTaskComparer allows comparion of WaitTasks, ignoring private fields.
  1867  func waitTaskComparer() cmp.Option {
  1868  	return cmp.Comparer(func(x, y *taskrunner.WaitTask) bool {
  1869  		if x == nil {
  1870  			return y == nil
  1871  		}
  1872  		if y == nil {
  1873  			return false
  1874  		}
  1875  		return x.TaskName == y.TaskName &&
  1876  			x.Ids.Hash() == y.Ids.Hash() && // exact order match
  1877  			x.Condition == y.Condition &&
  1878  			x.Timeout == y.Timeout &&
  1879  			cmp.Equal(x.Mapper, y.Mapper)
  1880  	})
  1881  }
  1882  
  1883  // fakeClientComparer allows comparion of inventory.FakeClient, ignoring objs.
  1884  func fakeClientComparer() cmp.Option {
  1885  	return cmp.Comparer(func(x, y *inventory.FakeClient) bool {
  1886  		if x == nil {
  1887  			return y == nil
  1888  		}
  1889  		if y == nil {
  1890  			return false
  1891  		}
  1892  		return true
  1893  	})
  1894  }
  1895  
  1896  // inventoryInfoComparer allows comparion of inventory.Info, ignoring impl.
  1897  func inventoryInfoComparer() cmp.Option {
  1898  	return cmp.Comparer(func(x, y inventory.Info) bool {
  1899  		return x.ID() == y.ID() &&
  1900  			x.Name() == y.Name() &&
  1901  			x.Namespace() == y.Namespace() &&
  1902  			x.Strategy() == y.Strategy()
  1903  	})
  1904  }
  1905  

View as plain text