github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/nomad/structs/diff_test.go (about)

     1  package structs
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestJobDiff(t *testing.T) {
    10  	cases := []struct {
    11  		Old, New   *Job
    12  		Expected   *JobDiff
    13  		Error      bool
    14  		Contextual bool
    15  	}{
    16  		{
    17  			Old: nil,
    18  			New: nil,
    19  			Expected: &JobDiff{
    20  				Type: DiffTypeNone,
    21  			},
    22  		},
    23  		{
    24  			// Different IDs
    25  			Old: &Job{
    26  				ID: "foo",
    27  			},
    28  			New: &Job{
    29  				ID: "bar",
    30  			},
    31  			Error: true,
    32  		},
    33  		{
    34  			// Primitive only that is the same
    35  			Old: &Job{
    36  				Region:    "foo",
    37  				ID:        "foo",
    38  				Name:      "foo",
    39  				Type:      "batch",
    40  				Priority:  10,
    41  				AllAtOnce: true,
    42  				Meta: map[string]string{
    43  					"foo": "bar",
    44  				},
    45  			},
    46  			New: &Job{
    47  				Region:    "foo",
    48  				ID:        "foo",
    49  				Name:      "foo",
    50  				Type:      "batch",
    51  				Priority:  10,
    52  				AllAtOnce: true,
    53  				Meta: map[string]string{
    54  					"foo": "bar",
    55  				},
    56  			},
    57  			Expected: &JobDiff{
    58  				Type: DiffTypeNone,
    59  				ID:   "foo",
    60  			},
    61  		},
    62  		{
    63  			// Primitive only that is has diffs
    64  			Old: &Job{
    65  				Region:    "foo",
    66  				ID:        "foo",
    67  				Name:      "foo",
    68  				Type:      "batch",
    69  				Priority:  10,
    70  				AllAtOnce: true,
    71  				Meta: map[string]string{
    72  					"foo": "bar",
    73  				},
    74  			},
    75  			New: &Job{
    76  				Region:    "bar",
    77  				ID:        "foo",
    78  				Name:      "bar",
    79  				Type:      "system",
    80  				Priority:  100,
    81  				AllAtOnce: false,
    82  				Meta: map[string]string{
    83  					"foo": "baz",
    84  				},
    85  			},
    86  			Expected: &JobDiff{
    87  				Type: DiffTypeEdited,
    88  				ID:   "foo",
    89  				Fields: []*FieldDiff{
    90  					{
    91  						Type: DiffTypeEdited,
    92  						Name: "AllAtOnce",
    93  						Old:  "true",
    94  						New:  "false",
    95  					},
    96  					{
    97  						Type: DiffTypeEdited,
    98  						Name: "Meta[foo]",
    99  						Old:  "bar",
   100  						New:  "baz",
   101  					},
   102  					{
   103  						Type: DiffTypeEdited,
   104  						Name: "Name",
   105  						Old:  "foo",
   106  						New:  "bar",
   107  					},
   108  					{
   109  						Type: DiffTypeEdited,
   110  						Name: "Priority",
   111  						Old:  "10",
   112  						New:  "100",
   113  					},
   114  					{
   115  						Type: DiffTypeEdited,
   116  						Name: "Region",
   117  						Old:  "foo",
   118  						New:  "bar",
   119  					},
   120  					{
   121  						Type: DiffTypeEdited,
   122  						Name: "Type",
   123  						Old:  "batch",
   124  						New:  "system",
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			// Primitive only deleted job
   131  			Old: &Job{
   132  				Region:    "foo",
   133  				ID:        "foo",
   134  				Name:      "foo",
   135  				Type:      "batch",
   136  				Priority:  10,
   137  				AllAtOnce: true,
   138  				Meta: map[string]string{
   139  					"foo": "bar",
   140  				},
   141  			},
   142  			New: nil,
   143  			Expected: &JobDiff{
   144  				Type: DiffTypeDeleted,
   145  				ID:   "foo",
   146  				Fields: []*FieldDiff{
   147  					{
   148  						Type: DiffTypeDeleted,
   149  						Name: "AllAtOnce",
   150  						Old:  "true",
   151  						New:  "",
   152  					},
   153  					{
   154  						Type: DiffTypeDeleted,
   155  						Name: "Meta[foo]",
   156  						Old:  "bar",
   157  						New:  "",
   158  					},
   159  					{
   160  						Type: DiffTypeDeleted,
   161  						Name: "Name",
   162  						Old:  "foo",
   163  						New:  "",
   164  					},
   165  					{
   166  						Type: DiffTypeDeleted,
   167  						Name: "Priority",
   168  						Old:  "10",
   169  						New:  "",
   170  					},
   171  					{
   172  						Type: DiffTypeDeleted,
   173  						Name: "Region",
   174  						Old:  "foo",
   175  						New:  "",
   176  					},
   177  					{
   178  						Type: DiffTypeDeleted,
   179  						Name: "Stop",
   180  						Old:  "false",
   181  						New:  "",
   182  					},
   183  					{
   184  						Type: DiffTypeDeleted,
   185  						Name: "Type",
   186  						Old:  "batch",
   187  						New:  "",
   188  					},
   189  				},
   190  				Objects: []*ObjectDiff{
   191  					{
   192  						Type: DiffTypeDeleted,
   193  						Name: "Update",
   194  						Fields: []*FieldDiff{
   195  							{
   196  								Type: DiffTypeDeleted,
   197  								Name: "MaxParallel",
   198  								Old:  "0",
   199  								New:  "",
   200  							},
   201  							{
   202  								Type: DiffTypeDeleted,
   203  								Name: "Stagger",
   204  								Old:  "0",
   205  								New:  "",
   206  							},
   207  						},
   208  					},
   209  				},
   210  			},
   211  		},
   212  		{
   213  			// Primitive only added job
   214  			Old: nil,
   215  			New: &Job{
   216  				Region:    "foo",
   217  				ID:        "foo",
   218  				Name:      "foo",
   219  				Type:      "batch",
   220  				Priority:  10,
   221  				AllAtOnce: true,
   222  				Meta: map[string]string{
   223  					"foo": "bar",
   224  				},
   225  			},
   226  			Expected: &JobDiff{
   227  				Type: DiffTypeAdded,
   228  				ID:   "foo",
   229  				Fields: []*FieldDiff{
   230  					{
   231  						Type: DiffTypeAdded,
   232  						Name: "AllAtOnce",
   233  						Old:  "",
   234  						New:  "true",
   235  					},
   236  					{
   237  						Type: DiffTypeAdded,
   238  						Name: "Meta[foo]",
   239  						Old:  "",
   240  						New:  "bar",
   241  					},
   242  					{
   243  						Type: DiffTypeAdded,
   244  						Name: "Name",
   245  						Old:  "",
   246  						New:  "foo",
   247  					},
   248  					{
   249  						Type: DiffTypeAdded,
   250  						Name: "Priority",
   251  						Old:  "",
   252  						New:  "10",
   253  					},
   254  					{
   255  						Type: DiffTypeAdded,
   256  						Name: "Region",
   257  						Old:  "",
   258  						New:  "foo",
   259  					},
   260  					{
   261  						Type: DiffTypeAdded,
   262  						Name: "Stop",
   263  						Old:  "",
   264  						New:  "false",
   265  					},
   266  					{
   267  						Type: DiffTypeAdded,
   268  						Name: "Type",
   269  						Old:  "",
   270  						New:  "batch",
   271  					},
   272  				},
   273  				Objects: []*ObjectDiff{
   274  					{
   275  						Type: DiffTypeAdded,
   276  						Name: "Update",
   277  						Fields: []*FieldDiff{
   278  							{
   279  								Type: DiffTypeAdded,
   280  								Name: "MaxParallel",
   281  								Old:  "",
   282  								New:  "0",
   283  							},
   284  							{
   285  								Type: DiffTypeAdded,
   286  								Name: "Stagger",
   287  								Old:  "",
   288  								New:  "0",
   289  							},
   290  						},
   291  					},
   292  				},
   293  			},
   294  		},
   295  		{
   296  			// Map diff
   297  			Old: &Job{
   298  				Meta: map[string]string{
   299  					"foo": "foo",
   300  					"bar": "bar",
   301  				},
   302  			},
   303  			New: &Job{
   304  				Meta: map[string]string{
   305  					"bar": "bar",
   306  					"baz": "baz",
   307  				},
   308  			},
   309  			Expected: &JobDiff{
   310  				Type: DiffTypeEdited,
   311  				Fields: []*FieldDiff{
   312  					{
   313  						Type: DiffTypeAdded,
   314  						Name: "Meta[baz]",
   315  						Old:  "",
   316  						New:  "baz",
   317  					},
   318  					{
   319  						Type: DiffTypeDeleted,
   320  						Name: "Meta[foo]",
   321  						Old:  "foo",
   322  						New:  "",
   323  					},
   324  				},
   325  			},
   326  		},
   327  		{
   328  			// Datacenter diff both added and removed
   329  			Old: &Job{
   330  				Datacenters: []string{"foo", "bar"},
   331  			},
   332  			New: &Job{
   333  				Datacenters: []string{"baz", "bar"},
   334  			},
   335  			Expected: &JobDiff{
   336  				Type: DiffTypeEdited,
   337  				Objects: []*ObjectDiff{
   338  					{
   339  						Type: DiffTypeEdited,
   340  						Name: "Datacenters",
   341  						Fields: []*FieldDiff{
   342  							{
   343  								Type: DiffTypeAdded,
   344  								Name: "Datacenters",
   345  								Old:  "",
   346  								New:  "baz",
   347  							},
   348  							{
   349  								Type: DiffTypeDeleted,
   350  								Name: "Datacenters",
   351  								Old:  "foo",
   352  								New:  "",
   353  							},
   354  						},
   355  					},
   356  				},
   357  			},
   358  		},
   359  		{
   360  			// Datacenter diff just added
   361  			Old: &Job{
   362  				Datacenters: []string{"foo", "bar"},
   363  			},
   364  			New: &Job{
   365  				Datacenters: []string{"foo", "bar", "baz"},
   366  			},
   367  			Expected: &JobDiff{
   368  				Type: DiffTypeEdited,
   369  				Objects: []*ObjectDiff{
   370  					{
   371  						Type: DiffTypeAdded,
   372  						Name: "Datacenters",
   373  						Fields: []*FieldDiff{
   374  							{
   375  								Type: DiffTypeAdded,
   376  								Name: "Datacenters",
   377  								Old:  "",
   378  								New:  "baz",
   379  							},
   380  						},
   381  					},
   382  				},
   383  			},
   384  		},
   385  		{
   386  			// Datacenter diff just deleted
   387  			Old: &Job{
   388  				Datacenters: []string{"foo", "bar"},
   389  			},
   390  			New: &Job{
   391  				Datacenters: []string{"foo"},
   392  			},
   393  			Expected: &JobDiff{
   394  				Type: DiffTypeEdited,
   395  				Objects: []*ObjectDiff{
   396  					{
   397  						Type: DiffTypeDeleted,
   398  						Name: "Datacenters",
   399  						Fields: []*FieldDiff{
   400  							{
   401  								Type: DiffTypeDeleted,
   402  								Name: "Datacenters",
   403  								Old:  "bar",
   404  								New:  "",
   405  							},
   406  						},
   407  					},
   408  				},
   409  			},
   410  		},
   411  		{
   412  			// Datacenter contextual no change
   413  			Contextual: true,
   414  			Old: &Job{
   415  				Datacenters: []string{"foo", "bar"},
   416  			},
   417  			New: &Job{
   418  				Datacenters: []string{"foo", "bar"},
   419  			},
   420  			Expected: &JobDiff{
   421  				Type: DiffTypeNone,
   422  			},
   423  		},
   424  		{
   425  			// Datacenter contextual
   426  			Contextual: true,
   427  			Old: &Job{
   428  				Datacenters: []string{"foo", "bar"},
   429  			},
   430  			New: &Job{
   431  				Datacenters: []string{"foo", "bar", "baz"},
   432  			},
   433  			Expected: &JobDiff{
   434  				Type: DiffTypeEdited,
   435  				Objects: []*ObjectDiff{
   436  					{
   437  						Type: DiffTypeAdded,
   438  						Name: "Datacenters",
   439  						Fields: []*FieldDiff{
   440  							{
   441  								Type: DiffTypeAdded,
   442  								Name: "Datacenters",
   443  								Old:  "",
   444  								New:  "baz",
   445  							},
   446  							{
   447  								Type: DiffTypeNone,
   448  								Name: "Datacenters",
   449  								Old:  "bar",
   450  								New:  "bar",
   451  							},
   452  							{
   453  								Type: DiffTypeNone,
   454  								Name: "Datacenters",
   455  								Old:  "foo",
   456  								New:  "foo",
   457  							},
   458  						},
   459  					},
   460  				},
   461  			},
   462  		},
   463  		{
   464  			// Update strategy edited
   465  			Old: &Job{
   466  				Update: UpdateStrategy{
   467  					Stagger:     10 * time.Second,
   468  					MaxParallel: 5,
   469  				},
   470  			},
   471  			New: &Job{
   472  				Update: UpdateStrategy{
   473  					Stagger:     60 * time.Second,
   474  					MaxParallel: 10,
   475  				},
   476  			},
   477  			Expected: &JobDiff{
   478  				Type: DiffTypeEdited,
   479  				Objects: []*ObjectDiff{
   480  					{
   481  						Type: DiffTypeEdited,
   482  						Name: "Update",
   483  						Fields: []*FieldDiff{
   484  							{
   485  								Type: DiffTypeEdited,
   486  								Name: "MaxParallel",
   487  								Old:  "5",
   488  								New:  "10",
   489  							},
   490  							{
   491  								Type: DiffTypeEdited,
   492  								Name: "Stagger",
   493  								Old:  "10000000000",
   494  								New:  "60000000000",
   495  							},
   496  						},
   497  					},
   498  				},
   499  			},
   500  		},
   501  		{
   502  			// Update strategy edited with context
   503  			Contextual: true,
   504  			Old: &Job{
   505  				Update: UpdateStrategy{
   506  					Stagger:     10 * time.Second,
   507  					MaxParallel: 5,
   508  				},
   509  			},
   510  			New: &Job{
   511  				Update: UpdateStrategy{
   512  					Stagger:     60 * time.Second,
   513  					MaxParallel: 5,
   514  				},
   515  			},
   516  			Expected: &JobDiff{
   517  				Type: DiffTypeEdited,
   518  				Objects: []*ObjectDiff{
   519  					{
   520  						Type: DiffTypeEdited,
   521  						Name: "Update",
   522  						Fields: []*FieldDiff{
   523  							{
   524  								Type: DiffTypeNone,
   525  								Name: "MaxParallel",
   526  								Old:  "5",
   527  								New:  "5",
   528  							},
   529  							{
   530  								Type: DiffTypeEdited,
   531  								Name: "Stagger",
   532  								Old:  "10000000000",
   533  								New:  "60000000000",
   534  							},
   535  						},
   536  					},
   537  				},
   538  			},
   539  		},
   540  		{
   541  			// Periodic added
   542  			Old: &Job{},
   543  			New: &Job{
   544  				Periodic: &PeriodicConfig{
   545  					Enabled:         false,
   546  					Spec:            "*/15 * * * * *",
   547  					SpecType:        "foo",
   548  					ProhibitOverlap: false,
   549  					TimeZone:        "Europe/Minsk",
   550  				},
   551  			},
   552  			Expected: &JobDiff{
   553  				Type: DiffTypeEdited,
   554  				Objects: []*ObjectDiff{
   555  					{
   556  						Type: DiffTypeAdded,
   557  						Name: "Periodic",
   558  						Fields: []*FieldDiff{
   559  							{
   560  								Type: DiffTypeAdded,
   561  								Name: "Enabled",
   562  								Old:  "",
   563  								New:  "false",
   564  							},
   565  							{
   566  								Type: DiffTypeAdded,
   567  								Name: "ProhibitOverlap",
   568  								Old:  "",
   569  								New:  "false",
   570  							},
   571  							{
   572  								Type: DiffTypeAdded,
   573  								Name: "Spec",
   574  								Old:  "",
   575  								New:  "*/15 * * * * *",
   576  							},
   577  							{
   578  								Type: DiffTypeAdded,
   579  								Name: "SpecType",
   580  								Old:  "",
   581  								New:  "foo",
   582  							},
   583  							{
   584  								Type: DiffTypeAdded,
   585  								Name: "TimeZone",
   586  								Old:  "",
   587  								New:  "Europe/Minsk",
   588  							},
   589  						},
   590  					},
   591  				},
   592  			},
   593  		},
   594  		{
   595  			// Periodic deleted
   596  			Old: &Job{
   597  				Periodic: &PeriodicConfig{
   598  					Enabled:         false,
   599  					Spec:            "*/15 * * * * *",
   600  					SpecType:        "foo",
   601  					ProhibitOverlap: false,
   602  					TimeZone:        "Europe/Minsk",
   603  				},
   604  			},
   605  			New: &Job{},
   606  			Expected: &JobDiff{
   607  				Type: DiffTypeEdited,
   608  				Objects: []*ObjectDiff{
   609  					{
   610  						Type: DiffTypeDeleted,
   611  						Name: "Periodic",
   612  						Fields: []*FieldDiff{
   613  							{
   614  								Type: DiffTypeDeleted,
   615  								Name: "Enabled",
   616  								Old:  "false",
   617  								New:  "",
   618  							},
   619  							{
   620  								Type: DiffTypeDeleted,
   621  								Name: "ProhibitOverlap",
   622  								Old:  "false",
   623  								New:  "",
   624  							},
   625  							{
   626  								Type: DiffTypeDeleted,
   627  								Name: "Spec",
   628  								Old:  "*/15 * * * * *",
   629  								New:  "",
   630  							},
   631  							{
   632  								Type: DiffTypeDeleted,
   633  								Name: "SpecType",
   634  								Old:  "foo",
   635  								New:  "",
   636  							},
   637  							{
   638  								Type: DiffTypeDeleted,
   639  								Name: "TimeZone",
   640  								Old:  "Europe/Minsk",
   641  								New:  "",
   642  							},
   643  						},
   644  					},
   645  				},
   646  			},
   647  		},
   648  		{
   649  			// Periodic edited
   650  			Old: &Job{
   651  				Periodic: &PeriodicConfig{
   652  					Enabled:         false,
   653  					Spec:            "*/15 * * * * *",
   654  					SpecType:        "foo",
   655  					ProhibitOverlap: false,
   656  					TimeZone:        "Europe/Minsk",
   657  				},
   658  			},
   659  			New: &Job{
   660  				Periodic: &PeriodicConfig{
   661  					Enabled:         true,
   662  					Spec:            "* * * * * *",
   663  					SpecType:        "cron",
   664  					ProhibitOverlap: true,
   665  					TimeZone:        "America/Los_Angeles",
   666  				},
   667  			},
   668  			Expected: &JobDiff{
   669  				Type: DiffTypeEdited,
   670  				Objects: []*ObjectDiff{
   671  					{
   672  						Type: DiffTypeEdited,
   673  						Name: "Periodic",
   674  						Fields: []*FieldDiff{
   675  							{
   676  								Type: DiffTypeEdited,
   677  								Name: "Enabled",
   678  								Old:  "false",
   679  								New:  "true",
   680  							},
   681  							{
   682  								Type: DiffTypeEdited,
   683  								Name: "ProhibitOverlap",
   684  								Old:  "false",
   685  								New:  "true",
   686  							},
   687  							{
   688  								Type: DiffTypeEdited,
   689  								Name: "Spec",
   690  								Old:  "*/15 * * * * *",
   691  								New:  "* * * * * *",
   692  							},
   693  							{
   694  								Type: DiffTypeEdited,
   695  								Name: "SpecType",
   696  								Old:  "foo",
   697  								New:  "cron",
   698  							},
   699  							{
   700  								Type: DiffTypeEdited,
   701  								Name: "TimeZone",
   702  								Old:  "Europe/Minsk",
   703  								New:  "America/Los_Angeles",
   704  							},
   705  						},
   706  					},
   707  				},
   708  			},
   709  		},
   710  		{
   711  			// Periodic edited with context
   712  			Contextual: true,
   713  			Old: &Job{
   714  				Periodic: &PeriodicConfig{
   715  					Enabled:         false,
   716  					Spec:            "*/15 * * * * *",
   717  					SpecType:        "foo",
   718  					ProhibitOverlap: false,
   719  					TimeZone:        "Europe/Minsk",
   720  				},
   721  			},
   722  			New: &Job{
   723  				Periodic: &PeriodicConfig{
   724  					Enabled:         true,
   725  					Spec:            "* * * * * *",
   726  					SpecType:        "foo",
   727  					ProhibitOverlap: false,
   728  					TimeZone:        "Europe/Minsk",
   729  				},
   730  			},
   731  			Expected: &JobDiff{
   732  				Type: DiffTypeEdited,
   733  				Objects: []*ObjectDiff{
   734  					{
   735  						Type: DiffTypeEdited,
   736  						Name: "Periodic",
   737  						Fields: []*FieldDiff{
   738  							{
   739  								Type: DiffTypeEdited,
   740  								Name: "Enabled",
   741  								Old:  "false",
   742  								New:  "true",
   743  							},
   744  							{
   745  								Type: DiffTypeNone,
   746  								Name: "ProhibitOverlap",
   747  								Old:  "false",
   748  								New:  "false",
   749  							},
   750  							{
   751  								Type: DiffTypeEdited,
   752  								Name: "Spec",
   753  								Old:  "*/15 * * * * *",
   754  								New:  "* * * * * *",
   755  							},
   756  							{
   757  								Type: DiffTypeNone,
   758  								Name: "SpecType",
   759  								Old:  "foo",
   760  								New:  "foo",
   761  							},
   762  							{
   763  								Type: DiffTypeNone,
   764  								Name: "TimeZone",
   765  								Old:  "Europe/Minsk",
   766  								New:  "Europe/Minsk",
   767  							},
   768  						},
   769  					},
   770  				},
   771  			},
   772  		},
   773  		{
   774  			// Constraints edited
   775  			Old: &Job{
   776  				Constraints: []*Constraint{
   777  					{
   778  						LTarget: "foo",
   779  						RTarget: "foo",
   780  						Operand: "foo",
   781  						str:     "foo",
   782  					},
   783  					{
   784  						LTarget: "bar",
   785  						RTarget: "bar",
   786  						Operand: "bar",
   787  						str:     "bar",
   788  					},
   789  				},
   790  			},
   791  			New: &Job{
   792  				Constraints: []*Constraint{
   793  					{
   794  						LTarget: "foo",
   795  						RTarget: "foo",
   796  						Operand: "foo",
   797  						str:     "foo",
   798  					},
   799  					{
   800  						LTarget: "baz",
   801  						RTarget: "baz",
   802  						Operand: "baz",
   803  						str:     "baz",
   804  					},
   805  				},
   806  			},
   807  			Expected: &JobDiff{
   808  				Type: DiffTypeEdited,
   809  				Objects: []*ObjectDiff{
   810  					{
   811  						Type: DiffTypeAdded,
   812  						Name: "Constraint",
   813  						Fields: []*FieldDiff{
   814  							{
   815  								Type: DiffTypeAdded,
   816  								Name: "LTarget",
   817  								Old:  "",
   818  								New:  "baz",
   819  							},
   820  							{
   821  								Type: DiffTypeAdded,
   822  								Name: "Operand",
   823  								Old:  "",
   824  								New:  "baz",
   825  							},
   826  							{
   827  								Type: DiffTypeAdded,
   828  								Name: "RTarget",
   829  								Old:  "",
   830  								New:  "baz",
   831  							},
   832  						},
   833  					},
   834  					{
   835  						Type: DiffTypeDeleted,
   836  						Name: "Constraint",
   837  						Fields: []*FieldDiff{
   838  							{
   839  								Type: DiffTypeDeleted,
   840  								Name: "LTarget",
   841  								Old:  "bar",
   842  								New:  "",
   843  							},
   844  							{
   845  								Type: DiffTypeDeleted,
   846  								Name: "Operand",
   847  								Old:  "bar",
   848  								New:  "",
   849  							},
   850  							{
   851  								Type: DiffTypeDeleted,
   852  								Name: "RTarget",
   853  								Old:  "bar",
   854  								New:  "",
   855  							},
   856  						},
   857  					},
   858  				},
   859  			},
   860  		},
   861  		{
   862  			// Task groups edited
   863  			Old: &Job{
   864  				TaskGroups: []*TaskGroup{
   865  					{
   866  						Name:  "foo",
   867  						Count: 1,
   868  					},
   869  					{
   870  						Name:  "bar",
   871  						Count: 1,
   872  					},
   873  					{
   874  						Name:  "baz",
   875  						Count: 1,
   876  					},
   877  				},
   878  			},
   879  			New: &Job{
   880  				TaskGroups: []*TaskGroup{
   881  					{
   882  						Name:  "bar",
   883  						Count: 1,
   884  					},
   885  					{
   886  						Name:  "baz",
   887  						Count: 2,
   888  					},
   889  					{
   890  						Name:  "bam",
   891  						Count: 1,
   892  					},
   893  				},
   894  			},
   895  			Expected: &JobDiff{
   896  				Type: DiffTypeEdited,
   897  				TaskGroups: []*TaskGroupDiff{
   898  					{
   899  						Type: DiffTypeAdded,
   900  						Name: "bam",
   901  						Fields: []*FieldDiff{
   902  							{
   903  								Type: DiffTypeAdded,
   904  								Name: "Count",
   905  								Old:  "",
   906  								New:  "1",
   907  							},
   908  						},
   909  					},
   910  					{
   911  						Type: DiffTypeNone,
   912  						Name: "bar",
   913  					},
   914  					{
   915  						Type: DiffTypeEdited,
   916  						Name: "baz",
   917  						Fields: []*FieldDiff{
   918  							{
   919  								Type: DiffTypeEdited,
   920  								Name: "Count",
   921  								Old:  "1",
   922  								New:  "2",
   923  							},
   924  						},
   925  					},
   926  					{
   927  						Type: DiffTypeDeleted,
   928  						Name: "foo",
   929  						Fields: []*FieldDiff{
   930  							{
   931  								Type: DiffTypeDeleted,
   932  								Name: "Count",
   933  								Old:  "1",
   934  								New:  "",
   935  							},
   936  						},
   937  					},
   938  				},
   939  			},
   940  		},
   941  		{
   942  			// Parameterized Job added
   943  			Old: &Job{},
   944  			New: &Job{
   945  				ParameterizedJob: &ParameterizedJobConfig{
   946  					Payload:      DispatchPayloadRequired,
   947  					MetaOptional: []string{"foo"},
   948  					MetaRequired: []string{"bar"},
   949  				},
   950  			},
   951  			Expected: &JobDiff{
   952  				Type: DiffTypeEdited,
   953  				Objects: []*ObjectDiff{
   954  					{
   955  						Type: DiffTypeAdded,
   956  						Name: "ParameterizedJob",
   957  						Fields: []*FieldDiff{
   958  							{
   959  								Type: DiffTypeAdded,
   960  								Name: "Payload",
   961  								Old:  "",
   962  								New:  DispatchPayloadRequired,
   963  							},
   964  						},
   965  						Objects: []*ObjectDiff{
   966  							{
   967  								Type: DiffTypeAdded,
   968  								Name: "MetaOptional",
   969  								Fields: []*FieldDiff{
   970  									{
   971  										Type: DiffTypeAdded,
   972  										Name: "MetaOptional",
   973  										Old:  "",
   974  										New:  "foo",
   975  									},
   976  								},
   977  							},
   978  							{
   979  								Type: DiffTypeAdded,
   980  								Name: "MetaRequired",
   981  								Fields: []*FieldDiff{
   982  									{
   983  										Type: DiffTypeAdded,
   984  										Name: "MetaRequired",
   985  										Old:  "",
   986  										New:  "bar",
   987  									},
   988  								},
   989  							},
   990  						},
   991  					},
   992  				},
   993  			},
   994  		},
   995  		{
   996  			// Parameterized Job deleted
   997  			Old: &Job{
   998  				ParameterizedJob: &ParameterizedJobConfig{
   999  					Payload:      DispatchPayloadRequired,
  1000  					MetaOptional: []string{"foo"},
  1001  					MetaRequired: []string{"bar"},
  1002  				},
  1003  			},
  1004  			New: &Job{},
  1005  			Expected: &JobDiff{
  1006  				Type: DiffTypeEdited,
  1007  				Objects: []*ObjectDiff{
  1008  					{
  1009  						Type: DiffTypeDeleted,
  1010  						Name: "ParameterizedJob",
  1011  						Fields: []*FieldDiff{
  1012  							{
  1013  								Type: DiffTypeDeleted,
  1014  								Name: "Payload",
  1015  								Old:  DispatchPayloadRequired,
  1016  								New:  "",
  1017  							},
  1018  						},
  1019  						Objects: []*ObjectDiff{
  1020  							{
  1021  								Type: DiffTypeDeleted,
  1022  								Name: "MetaOptional",
  1023  								Fields: []*FieldDiff{
  1024  									{
  1025  										Type: DiffTypeDeleted,
  1026  										Name: "MetaOptional",
  1027  										Old:  "foo",
  1028  										New:  "",
  1029  									},
  1030  								},
  1031  							},
  1032  							{
  1033  								Type: DiffTypeDeleted,
  1034  								Name: "MetaRequired",
  1035  								Fields: []*FieldDiff{
  1036  									{
  1037  										Type: DiffTypeDeleted,
  1038  										Name: "MetaRequired",
  1039  										Old:  "bar",
  1040  										New:  "",
  1041  									},
  1042  								},
  1043  							},
  1044  						},
  1045  					},
  1046  				},
  1047  			},
  1048  		},
  1049  		{
  1050  			// Parameterized Job edited
  1051  			Old: &Job{
  1052  				ParameterizedJob: &ParameterizedJobConfig{
  1053  					Payload:      DispatchPayloadRequired,
  1054  					MetaOptional: []string{"foo"},
  1055  					MetaRequired: []string{"bar"},
  1056  				},
  1057  			},
  1058  			New: &Job{
  1059  				ParameterizedJob: &ParameterizedJobConfig{
  1060  					Payload:      DispatchPayloadOptional,
  1061  					MetaOptional: []string{"bam"},
  1062  					MetaRequired: []string{"bang"},
  1063  				},
  1064  			},
  1065  			Expected: &JobDiff{
  1066  				Type: DiffTypeEdited,
  1067  				Objects: []*ObjectDiff{
  1068  					{
  1069  						Type: DiffTypeEdited,
  1070  						Name: "ParameterizedJob",
  1071  						Fields: []*FieldDiff{
  1072  							{
  1073  								Type: DiffTypeEdited,
  1074  								Name: "Payload",
  1075  								Old:  DispatchPayloadRequired,
  1076  								New:  DispatchPayloadOptional,
  1077  							},
  1078  						},
  1079  						Objects: []*ObjectDiff{
  1080  							{
  1081  								Type: DiffTypeEdited,
  1082  								Name: "MetaOptional",
  1083  								Fields: []*FieldDiff{
  1084  									{
  1085  										Type: DiffTypeAdded,
  1086  										Name: "MetaOptional",
  1087  										Old:  "",
  1088  										New:  "bam",
  1089  									},
  1090  									{
  1091  										Type: DiffTypeDeleted,
  1092  										Name: "MetaOptional",
  1093  										Old:  "foo",
  1094  										New:  "",
  1095  									},
  1096  								},
  1097  							},
  1098  							{
  1099  								Type: DiffTypeEdited,
  1100  								Name: "MetaRequired",
  1101  								Fields: []*FieldDiff{
  1102  									{
  1103  										Type: DiffTypeAdded,
  1104  										Name: "MetaRequired",
  1105  										Old:  "",
  1106  										New:  "bang",
  1107  									},
  1108  									{
  1109  										Type: DiffTypeDeleted,
  1110  										Name: "MetaRequired",
  1111  										Old:  "bar",
  1112  										New:  "",
  1113  									},
  1114  								},
  1115  							},
  1116  						},
  1117  					},
  1118  				},
  1119  			},
  1120  		},
  1121  		{
  1122  			// Parameterized Job edited with context
  1123  			Contextual: true,
  1124  			Old: &Job{
  1125  				ParameterizedJob: &ParameterizedJobConfig{
  1126  					Payload:      DispatchPayloadRequired,
  1127  					MetaOptional: []string{"foo"},
  1128  					MetaRequired: []string{"bar"},
  1129  				},
  1130  			},
  1131  			New: &Job{
  1132  				ParameterizedJob: &ParameterizedJobConfig{
  1133  					Payload:      DispatchPayloadOptional,
  1134  					MetaOptional: []string{"foo"},
  1135  					MetaRequired: []string{"bar"},
  1136  				},
  1137  			},
  1138  			Expected: &JobDiff{
  1139  				Type: DiffTypeEdited,
  1140  				Objects: []*ObjectDiff{
  1141  					{
  1142  						Type: DiffTypeEdited,
  1143  						Name: "ParameterizedJob",
  1144  						Fields: []*FieldDiff{
  1145  							{
  1146  								Type: DiffTypeEdited,
  1147  								Name: "Payload",
  1148  								Old:  DispatchPayloadRequired,
  1149  								New:  DispatchPayloadOptional,
  1150  							},
  1151  						},
  1152  						Objects: []*ObjectDiff{
  1153  							{
  1154  								Type: DiffTypeNone,
  1155  								Name: "MetaOptional",
  1156  								Fields: []*FieldDiff{
  1157  									{
  1158  										Type: DiffTypeNone,
  1159  										Name: "MetaOptional",
  1160  										Old:  "foo",
  1161  										New:  "foo",
  1162  									},
  1163  								},
  1164  							},
  1165  							{
  1166  								Type: DiffTypeNone,
  1167  								Name: "MetaRequired",
  1168  								Fields: []*FieldDiff{
  1169  									{
  1170  										Type: DiffTypeNone,
  1171  										Name: "MetaRequired",
  1172  										Old:  "bar",
  1173  										New:  "bar",
  1174  									},
  1175  								},
  1176  							},
  1177  						},
  1178  					},
  1179  				},
  1180  			},
  1181  		},
  1182  	}
  1183  
  1184  	for i, c := range cases {
  1185  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1186  		if c.Error && err == nil {
  1187  			t.Fatalf("case %d: expected errored", i+1)
  1188  		} else if err != nil {
  1189  			if !c.Error {
  1190  				t.Fatalf("case %d: errored %#v", i+1, err)
  1191  			} else {
  1192  				continue
  1193  			}
  1194  		}
  1195  
  1196  		if !reflect.DeepEqual(actual, c.Expected) {
  1197  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1198  				i+1, actual, c.Expected)
  1199  		}
  1200  	}
  1201  }
  1202  
  1203  func TestTaskGroupDiff(t *testing.T) {
  1204  	cases := []struct {
  1205  		Old, New   *TaskGroup
  1206  		Expected   *TaskGroupDiff
  1207  		Error      bool
  1208  		Contextual bool
  1209  	}{
  1210  		{
  1211  			Old: nil,
  1212  			New: nil,
  1213  			Expected: &TaskGroupDiff{
  1214  				Type: DiffTypeNone,
  1215  			},
  1216  		},
  1217  		{
  1218  			// Primitive only that has different names
  1219  			Old: &TaskGroup{
  1220  				Name:  "foo",
  1221  				Count: 10,
  1222  				Meta: map[string]string{
  1223  					"foo": "bar",
  1224  				},
  1225  			},
  1226  			New: &TaskGroup{
  1227  				Name:  "bar",
  1228  				Count: 10,
  1229  				Meta: map[string]string{
  1230  					"foo": "bar",
  1231  				},
  1232  			},
  1233  			Error: true,
  1234  		},
  1235  		{
  1236  			// Primitive only that is the same
  1237  			Old: &TaskGroup{
  1238  				Name:  "foo",
  1239  				Count: 10,
  1240  				Meta: map[string]string{
  1241  					"foo": "bar",
  1242  				},
  1243  			},
  1244  			New: &TaskGroup{
  1245  				Name:  "foo",
  1246  				Count: 10,
  1247  				Meta: map[string]string{
  1248  					"foo": "bar",
  1249  				},
  1250  			},
  1251  			Expected: &TaskGroupDiff{
  1252  				Type: DiffTypeNone,
  1253  				Name: "foo",
  1254  			},
  1255  		},
  1256  		{
  1257  			// Primitive only that has diffs
  1258  			Old: &TaskGroup{
  1259  				Name:  "foo",
  1260  				Count: 10,
  1261  				Meta: map[string]string{
  1262  					"foo": "bar",
  1263  				},
  1264  			},
  1265  			New: &TaskGroup{
  1266  				Name:  "foo",
  1267  				Count: 100,
  1268  				Meta: map[string]string{
  1269  					"foo": "baz",
  1270  				},
  1271  			},
  1272  			Expected: &TaskGroupDiff{
  1273  				Type: DiffTypeEdited,
  1274  				Name: "foo",
  1275  				Fields: []*FieldDiff{
  1276  					{
  1277  						Type: DiffTypeEdited,
  1278  						Name: "Count",
  1279  						Old:  "10",
  1280  						New:  "100",
  1281  					},
  1282  					{
  1283  						Type: DiffTypeEdited,
  1284  						Name: "Meta[foo]",
  1285  						Old:  "bar",
  1286  						New:  "baz",
  1287  					},
  1288  				},
  1289  			},
  1290  		},
  1291  		{
  1292  			// Map diff
  1293  			Old: &TaskGroup{
  1294  				Meta: map[string]string{
  1295  					"foo": "foo",
  1296  					"bar": "bar",
  1297  				},
  1298  			},
  1299  			New: &TaskGroup{
  1300  				Meta: map[string]string{
  1301  					"bar": "bar",
  1302  					"baz": "baz",
  1303  				},
  1304  			},
  1305  			Expected: &TaskGroupDiff{
  1306  				Type: DiffTypeEdited,
  1307  				Fields: []*FieldDiff{
  1308  					{
  1309  						Type: DiffTypeAdded,
  1310  						Name: "Meta[baz]",
  1311  						Old:  "",
  1312  						New:  "baz",
  1313  					},
  1314  					{
  1315  						Type: DiffTypeDeleted,
  1316  						Name: "Meta[foo]",
  1317  						Old:  "foo",
  1318  						New:  "",
  1319  					},
  1320  				},
  1321  			},
  1322  		},
  1323  		{
  1324  			// Constraints edited
  1325  			Old: &TaskGroup{
  1326  				Constraints: []*Constraint{
  1327  					{
  1328  						LTarget: "foo",
  1329  						RTarget: "foo",
  1330  						Operand: "foo",
  1331  						str:     "foo",
  1332  					},
  1333  					{
  1334  						LTarget: "bar",
  1335  						RTarget: "bar",
  1336  						Operand: "bar",
  1337  						str:     "bar",
  1338  					},
  1339  				},
  1340  			},
  1341  			New: &TaskGroup{
  1342  				Constraints: []*Constraint{
  1343  					{
  1344  						LTarget: "foo",
  1345  						RTarget: "foo",
  1346  						Operand: "foo",
  1347  						str:     "foo",
  1348  					},
  1349  					{
  1350  						LTarget: "baz",
  1351  						RTarget: "baz",
  1352  						Operand: "baz",
  1353  						str:     "baz",
  1354  					},
  1355  				},
  1356  			},
  1357  			Expected: &TaskGroupDiff{
  1358  				Type: DiffTypeEdited,
  1359  				Objects: []*ObjectDiff{
  1360  					{
  1361  						Type: DiffTypeAdded,
  1362  						Name: "Constraint",
  1363  						Fields: []*FieldDiff{
  1364  							{
  1365  								Type: DiffTypeAdded,
  1366  								Name: "LTarget",
  1367  								Old:  "",
  1368  								New:  "baz",
  1369  							},
  1370  							{
  1371  								Type: DiffTypeAdded,
  1372  								Name: "Operand",
  1373  								Old:  "",
  1374  								New:  "baz",
  1375  							},
  1376  							{
  1377  								Type: DiffTypeAdded,
  1378  								Name: "RTarget",
  1379  								Old:  "",
  1380  								New:  "baz",
  1381  							},
  1382  						},
  1383  					},
  1384  					{
  1385  						Type: DiffTypeDeleted,
  1386  						Name: "Constraint",
  1387  						Fields: []*FieldDiff{
  1388  							{
  1389  								Type: DiffTypeDeleted,
  1390  								Name: "LTarget",
  1391  								Old:  "bar",
  1392  								New:  "",
  1393  							},
  1394  							{
  1395  								Type: DiffTypeDeleted,
  1396  								Name: "Operand",
  1397  								Old:  "bar",
  1398  								New:  "",
  1399  							},
  1400  							{
  1401  								Type: DiffTypeDeleted,
  1402  								Name: "RTarget",
  1403  								Old:  "bar",
  1404  								New:  "",
  1405  							},
  1406  						},
  1407  					},
  1408  				},
  1409  			},
  1410  		},
  1411  		{
  1412  			// RestartPolicy added
  1413  			Old: &TaskGroup{},
  1414  			New: &TaskGroup{
  1415  				RestartPolicy: &RestartPolicy{
  1416  					Attempts: 1,
  1417  					Interval: 1 * time.Second,
  1418  					Delay:    1 * time.Second,
  1419  					Mode:     "fail",
  1420  				},
  1421  			},
  1422  			Expected: &TaskGroupDiff{
  1423  				Type: DiffTypeEdited,
  1424  				Objects: []*ObjectDiff{
  1425  					{
  1426  						Type: DiffTypeAdded,
  1427  						Name: "RestartPolicy",
  1428  						Fields: []*FieldDiff{
  1429  							{
  1430  								Type: DiffTypeAdded,
  1431  								Name: "Attempts",
  1432  								Old:  "",
  1433  								New:  "1",
  1434  							},
  1435  							{
  1436  								Type: DiffTypeAdded,
  1437  								Name: "Delay",
  1438  								Old:  "",
  1439  								New:  "1000000000",
  1440  							},
  1441  							{
  1442  								Type: DiffTypeAdded,
  1443  								Name: "Interval",
  1444  								Old:  "",
  1445  								New:  "1000000000",
  1446  							},
  1447  							{
  1448  								Type: DiffTypeAdded,
  1449  								Name: "Mode",
  1450  								Old:  "",
  1451  								New:  "fail",
  1452  							},
  1453  						},
  1454  					},
  1455  				},
  1456  			},
  1457  		},
  1458  		{
  1459  			// RestartPolicy deleted
  1460  			Old: &TaskGroup{
  1461  				RestartPolicy: &RestartPolicy{
  1462  					Attempts: 1,
  1463  					Interval: 1 * time.Second,
  1464  					Delay:    1 * time.Second,
  1465  					Mode:     "fail",
  1466  				},
  1467  			},
  1468  			New: &TaskGroup{},
  1469  			Expected: &TaskGroupDiff{
  1470  				Type: DiffTypeEdited,
  1471  				Objects: []*ObjectDiff{
  1472  					{
  1473  						Type: DiffTypeDeleted,
  1474  						Name: "RestartPolicy",
  1475  						Fields: []*FieldDiff{
  1476  							{
  1477  								Type: DiffTypeDeleted,
  1478  								Name: "Attempts",
  1479  								Old:  "1",
  1480  								New:  "",
  1481  							},
  1482  							{
  1483  								Type: DiffTypeDeleted,
  1484  								Name: "Delay",
  1485  								Old:  "1000000000",
  1486  								New:  "",
  1487  							},
  1488  							{
  1489  								Type: DiffTypeDeleted,
  1490  								Name: "Interval",
  1491  								Old:  "1000000000",
  1492  								New:  "",
  1493  							},
  1494  							{
  1495  								Type: DiffTypeDeleted,
  1496  								Name: "Mode",
  1497  								Old:  "fail",
  1498  								New:  "",
  1499  							},
  1500  						},
  1501  					},
  1502  				},
  1503  			},
  1504  		},
  1505  		{
  1506  			// RestartPolicy edited
  1507  			Old: &TaskGroup{
  1508  				RestartPolicy: &RestartPolicy{
  1509  					Attempts: 1,
  1510  					Interval: 1 * time.Second,
  1511  					Delay:    1 * time.Second,
  1512  					Mode:     "fail",
  1513  				},
  1514  			},
  1515  			New: &TaskGroup{
  1516  				RestartPolicy: &RestartPolicy{
  1517  					Attempts: 2,
  1518  					Interval: 2 * time.Second,
  1519  					Delay:    2 * time.Second,
  1520  					Mode:     "delay",
  1521  				},
  1522  			},
  1523  			Expected: &TaskGroupDiff{
  1524  				Type: DiffTypeEdited,
  1525  				Objects: []*ObjectDiff{
  1526  					{
  1527  						Type: DiffTypeEdited,
  1528  						Name: "RestartPolicy",
  1529  						Fields: []*FieldDiff{
  1530  							{
  1531  								Type: DiffTypeEdited,
  1532  								Name: "Attempts",
  1533  								Old:  "1",
  1534  								New:  "2",
  1535  							},
  1536  							{
  1537  								Type: DiffTypeEdited,
  1538  								Name: "Delay",
  1539  								Old:  "1000000000",
  1540  								New:  "2000000000",
  1541  							},
  1542  							{
  1543  								Type: DiffTypeEdited,
  1544  								Name: "Interval",
  1545  								Old:  "1000000000",
  1546  								New:  "2000000000",
  1547  							},
  1548  							{
  1549  								Type: DiffTypeEdited,
  1550  								Name: "Mode",
  1551  								Old:  "fail",
  1552  								New:  "delay",
  1553  							},
  1554  						},
  1555  					},
  1556  				},
  1557  			},
  1558  		},
  1559  		{
  1560  			// RestartPolicy edited with context
  1561  			Contextual: true,
  1562  			Old: &TaskGroup{
  1563  				RestartPolicy: &RestartPolicy{
  1564  					Attempts: 1,
  1565  					Interval: 1 * time.Second,
  1566  					Delay:    1 * time.Second,
  1567  					Mode:     "fail",
  1568  				},
  1569  			},
  1570  			New: &TaskGroup{
  1571  				RestartPolicy: &RestartPolicy{
  1572  					Attempts: 2,
  1573  					Interval: 2 * time.Second,
  1574  					Delay:    1 * time.Second,
  1575  					Mode:     "fail",
  1576  				},
  1577  			},
  1578  			Expected: &TaskGroupDiff{
  1579  				Type: DiffTypeEdited,
  1580  				Objects: []*ObjectDiff{
  1581  					{
  1582  						Type: DiffTypeEdited,
  1583  						Name: "RestartPolicy",
  1584  						Fields: []*FieldDiff{
  1585  							{
  1586  								Type: DiffTypeEdited,
  1587  								Name: "Attempts",
  1588  								Old:  "1",
  1589  								New:  "2",
  1590  							},
  1591  							{
  1592  								Type: DiffTypeNone,
  1593  								Name: "Delay",
  1594  								Old:  "1000000000",
  1595  								New:  "1000000000",
  1596  							},
  1597  							{
  1598  								Type: DiffTypeEdited,
  1599  								Name: "Interval",
  1600  								Old:  "1000000000",
  1601  								New:  "2000000000",
  1602  							},
  1603  							{
  1604  								Type: DiffTypeNone,
  1605  								Name: "Mode",
  1606  								Old:  "fail",
  1607  								New:  "fail",
  1608  							},
  1609  						},
  1610  					},
  1611  				},
  1612  			},
  1613  		},
  1614  		{
  1615  			// EphemeralDisk added
  1616  			Old: &TaskGroup{},
  1617  			New: &TaskGroup{
  1618  				EphemeralDisk: &EphemeralDisk{
  1619  					Migrate: true,
  1620  					Sticky:  true,
  1621  					SizeMB:  100,
  1622  				},
  1623  			},
  1624  			Expected: &TaskGroupDiff{
  1625  				Type: DiffTypeEdited,
  1626  				Objects: []*ObjectDiff{
  1627  					{
  1628  						Type: DiffTypeAdded,
  1629  						Name: "EphemeralDisk",
  1630  						Fields: []*FieldDiff{
  1631  							{
  1632  								Type: DiffTypeAdded,
  1633  								Name: "Migrate",
  1634  								Old:  "",
  1635  								New:  "true",
  1636  							},
  1637  							{
  1638  								Type: DiffTypeAdded,
  1639  								Name: "SizeMB",
  1640  								Old:  "",
  1641  								New:  "100",
  1642  							},
  1643  							{
  1644  								Type: DiffTypeAdded,
  1645  								Name: "Sticky",
  1646  								Old:  "",
  1647  								New:  "true",
  1648  							},
  1649  						},
  1650  					},
  1651  				},
  1652  			},
  1653  		},
  1654  		{
  1655  			// EphemeralDisk deleted
  1656  			Old: &TaskGroup{
  1657  				EphemeralDisk: &EphemeralDisk{
  1658  					Migrate: true,
  1659  					Sticky:  true,
  1660  					SizeMB:  100,
  1661  				},
  1662  			},
  1663  			New: &TaskGroup{},
  1664  			Expected: &TaskGroupDiff{
  1665  				Type: DiffTypeEdited,
  1666  				Objects: []*ObjectDiff{
  1667  					{
  1668  						Type: DiffTypeDeleted,
  1669  						Name: "EphemeralDisk",
  1670  						Fields: []*FieldDiff{
  1671  							{
  1672  								Type: DiffTypeDeleted,
  1673  								Name: "Migrate",
  1674  								Old:  "true",
  1675  								New:  "",
  1676  							},
  1677  							{
  1678  								Type: DiffTypeDeleted,
  1679  								Name: "SizeMB",
  1680  								Old:  "100",
  1681  								New:  "",
  1682  							},
  1683  							{
  1684  								Type: DiffTypeDeleted,
  1685  								Name: "Sticky",
  1686  								Old:  "true",
  1687  								New:  "",
  1688  							},
  1689  						},
  1690  					},
  1691  				},
  1692  			},
  1693  		},
  1694  		{
  1695  			// EphemeralDisk edited
  1696  			Old: &TaskGroup{
  1697  				EphemeralDisk: &EphemeralDisk{
  1698  					Migrate: true,
  1699  					Sticky:  true,
  1700  					SizeMB:  150,
  1701  				},
  1702  			},
  1703  			New: &TaskGroup{
  1704  				EphemeralDisk: &EphemeralDisk{
  1705  					Migrate: false,
  1706  					Sticky:  false,
  1707  					SizeMB:  90,
  1708  				},
  1709  			},
  1710  			Expected: &TaskGroupDiff{
  1711  				Type: DiffTypeEdited,
  1712  				Objects: []*ObjectDiff{
  1713  					{
  1714  						Type: DiffTypeEdited,
  1715  						Name: "EphemeralDisk",
  1716  						Fields: []*FieldDiff{
  1717  							{
  1718  								Type: DiffTypeEdited,
  1719  								Name: "Migrate",
  1720  								Old:  "true",
  1721  								New:  "false",
  1722  							},
  1723  							{
  1724  								Type: DiffTypeEdited,
  1725  								Name: "SizeMB",
  1726  								Old:  "150",
  1727  								New:  "90",
  1728  							},
  1729  
  1730  							{
  1731  								Type: DiffTypeEdited,
  1732  								Name: "Sticky",
  1733  								Old:  "true",
  1734  								New:  "false",
  1735  							},
  1736  						},
  1737  					},
  1738  				},
  1739  			},
  1740  		},
  1741  		{
  1742  			// EphemeralDisk edited with context
  1743  			Contextual: true,
  1744  			Old: &TaskGroup{
  1745  				EphemeralDisk: &EphemeralDisk{
  1746  					Migrate: false,
  1747  					Sticky:  false,
  1748  					SizeMB:  100,
  1749  				},
  1750  			},
  1751  			New: &TaskGroup{
  1752  				EphemeralDisk: &EphemeralDisk{
  1753  					Migrate: true,
  1754  					Sticky:  true,
  1755  					SizeMB:  90,
  1756  				},
  1757  			},
  1758  			Expected: &TaskGroupDiff{
  1759  				Type: DiffTypeEdited,
  1760  				Objects: []*ObjectDiff{
  1761  					{
  1762  						Type: DiffTypeEdited,
  1763  						Name: "EphemeralDisk",
  1764  						Fields: []*FieldDiff{
  1765  							{
  1766  								Type: DiffTypeEdited,
  1767  								Name: "Migrate",
  1768  								Old:  "false",
  1769  								New:  "true",
  1770  							},
  1771  							{
  1772  								Type: DiffTypeEdited,
  1773  								Name: "SizeMB",
  1774  								Old:  "100",
  1775  								New:  "90",
  1776  							},
  1777  							{
  1778  								Type: DiffTypeEdited,
  1779  								Name: "Sticky",
  1780  								Old:  "false",
  1781  								New:  "true",
  1782  							},
  1783  						},
  1784  					},
  1785  				},
  1786  			},
  1787  		},
  1788  		{
  1789  			// Tasks edited
  1790  			Old: &TaskGroup{
  1791  				Tasks: []*Task{
  1792  					{
  1793  						Name:   "foo",
  1794  						Driver: "docker",
  1795  					},
  1796  					{
  1797  						Name:   "bar",
  1798  						Driver: "docker",
  1799  					},
  1800  					{
  1801  						Name:   "baz",
  1802  						Driver: "docker",
  1803  					},
  1804  				},
  1805  			},
  1806  			New: &TaskGroup{
  1807  				Tasks: []*Task{
  1808  					{
  1809  						Name:   "bar",
  1810  						Driver: "docker",
  1811  					},
  1812  					{
  1813  						Name:   "baz",
  1814  						Driver: "exec",
  1815  					},
  1816  					{
  1817  						Name:   "bam",
  1818  						Driver: "docker",
  1819  					},
  1820  				},
  1821  			},
  1822  			Expected: &TaskGroupDiff{
  1823  				Type: DiffTypeEdited,
  1824  				Tasks: []*TaskDiff{
  1825  					{
  1826  						Type: DiffTypeAdded,
  1827  						Name: "bam",
  1828  						Fields: []*FieldDiff{
  1829  							{
  1830  								Type: DiffTypeAdded,
  1831  								Name: "Driver",
  1832  								Old:  "",
  1833  								New:  "docker",
  1834  							},
  1835  							{
  1836  								Type: DiffTypeAdded,
  1837  								Name: "KillTimeout",
  1838  								Old:  "",
  1839  								New:  "0",
  1840  							},
  1841  							{
  1842  								Type: DiffTypeAdded,
  1843  								Name: "Leader",
  1844  								Old:  "",
  1845  								New:  "false",
  1846  							},
  1847  						},
  1848  					},
  1849  					{
  1850  						Type: DiffTypeNone,
  1851  						Name: "bar",
  1852  					},
  1853  					{
  1854  						Type: DiffTypeEdited,
  1855  						Name: "baz",
  1856  						Fields: []*FieldDiff{
  1857  							{
  1858  								Type: DiffTypeEdited,
  1859  								Name: "Driver",
  1860  								Old:  "docker",
  1861  								New:  "exec",
  1862  							},
  1863  						},
  1864  					},
  1865  					{
  1866  						Type: DiffTypeDeleted,
  1867  						Name: "foo",
  1868  						Fields: []*FieldDiff{
  1869  							{
  1870  								Type: DiffTypeDeleted,
  1871  								Name: "Driver",
  1872  								Old:  "docker",
  1873  								New:  "",
  1874  							},
  1875  							{
  1876  								Type: DiffTypeDeleted,
  1877  								Name: "KillTimeout",
  1878  								Old:  "0",
  1879  								New:  "",
  1880  							},
  1881  							{
  1882  								Type: DiffTypeDeleted,
  1883  								Name: "Leader",
  1884  								Old:  "false",
  1885  								New:  "",
  1886  							},
  1887  						},
  1888  					},
  1889  				},
  1890  			},
  1891  		},
  1892  	}
  1893  
  1894  	for i, c := range cases {
  1895  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1896  		if c.Error && err == nil {
  1897  			t.Fatalf("case %d: expected errored", i+1)
  1898  		} else if err != nil {
  1899  			if !c.Error {
  1900  				t.Fatalf("case %d: errored %#v", i+1, err)
  1901  			} else {
  1902  				continue
  1903  			}
  1904  		}
  1905  
  1906  		if !reflect.DeepEqual(actual, c.Expected) {
  1907  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1908  				i+1, actual, c.Expected)
  1909  		}
  1910  	}
  1911  }
  1912  
  1913  func TestTaskDiff(t *testing.T) {
  1914  	cases := []struct {
  1915  		Name       string
  1916  		Old, New   *Task
  1917  		Expected   *TaskDiff
  1918  		Error      bool
  1919  		Contextual bool
  1920  	}{
  1921  		{
  1922  			Name: "Empty",
  1923  			Old:  nil,
  1924  			New:  nil,
  1925  			Expected: &TaskDiff{
  1926  				Type: DiffTypeNone,
  1927  			},
  1928  		},
  1929  		{
  1930  			Name: "Primitive only that has different names",
  1931  			Old: &Task{
  1932  				Name: "foo",
  1933  				Meta: map[string]string{
  1934  					"foo": "bar",
  1935  				},
  1936  			},
  1937  			New: &Task{
  1938  				Name: "bar",
  1939  				Meta: map[string]string{
  1940  					"foo": "bar",
  1941  				},
  1942  			},
  1943  			Error: true,
  1944  		},
  1945  		{
  1946  			Name: "Primitive only that is the same",
  1947  			Old: &Task{
  1948  				Name:   "foo",
  1949  				Driver: "exec",
  1950  				User:   "foo",
  1951  				Env: map[string]string{
  1952  					"FOO": "bar",
  1953  				},
  1954  				Meta: map[string]string{
  1955  					"foo": "bar",
  1956  				},
  1957  				KillTimeout: 1 * time.Second,
  1958  				Leader:      true,
  1959  			},
  1960  			New: &Task{
  1961  				Name:   "foo",
  1962  				Driver: "exec",
  1963  				User:   "foo",
  1964  				Env: map[string]string{
  1965  					"FOO": "bar",
  1966  				},
  1967  				Meta: map[string]string{
  1968  					"foo": "bar",
  1969  				},
  1970  				KillTimeout: 1 * time.Second,
  1971  				Leader:      true,
  1972  			},
  1973  			Expected: &TaskDiff{
  1974  				Type: DiffTypeNone,
  1975  				Name: "foo",
  1976  			},
  1977  		},
  1978  		{
  1979  			Name: "Primitive only that has diffs",
  1980  			Old: &Task{
  1981  				Name:   "foo",
  1982  				Driver: "exec",
  1983  				User:   "foo",
  1984  				Env: map[string]string{
  1985  					"FOO": "bar",
  1986  				},
  1987  				Meta: map[string]string{
  1988  					"foo": "bar",
  1989  				},
  1990  				KillTimeout: 1 * time.Second,
  1991  				Leader:      true,
  1992  			},
  1993  			New: &Task{
  1994  				Name:   "foo",
  1995  				Driver: "docker",
  1996  				User:   "bar",
  1997  				Env: map[string]string{
  1998  					"FOO": "baz",
  1999  				},
  2000  				Meta: map[string]string{
  2001  					"foo": "baz",
  2002  				},
  2003  				KillTimeout: 2 * time.Second,
  2004  				Leader:      false,
  2005  			},
  2006  			Expected: &TaskDiff{
  2007  				Type: DiffTypeEdited,
  2008  				Name: "foo",
  2009  				Fields: []*FieldDiff{
  2010  					{
  2011  						Type: DiffTypeEdited,
  2012  						Name: "Driver",
  2013  						Old:  "exec",
  2014  						New:  "docker",
  2015  					},
  2016  					{
  2017  						Type: DiffTypeEdited,
  2018  						Name: "Env[FOO]",
  2019  						Old:  "bar",
  2020  						New:  "baz",
  2021  					},
  2022  					{
  2023  						Type: DiffTypeEdited,
  2024  						Name: "KillTimeout",
  2025  						Old:  "1000000000",
  2026  						New:  "2000000000",
  2027  					},
  2028  					{
  2029  						Type: DiffTypeEdited,
  2030  						Name: "Leader",
  2031  						Old:  "true",
  2032  						New:  "false",
  2033  					},
  2034  					{
  2035  						Type: DiffTypeEdited,
  2036  						Name: "Meta[foo]",
  2037  						Old:  "bar",
  2038  						New:  "baz",
  2039  					},
  2040  					{
  2041  						Type: DiffTypeEdited,
  2042  						Name: "User",
  2043  						Old:  "foo",
  2044  						New:  "bar",
  2045  					},
  2046  				},
  2047  			},
  2048  		},
  2049  		{
  2050  			Name: "Map diff",
  2051  			Old: &Task{
  2052  				Meta: map[string]string{
  2053  					"foo": "foo",
  2054  					"bar": "bar",
  2055  				},
  2056  				Env: map[string]string{
  2057  					"foo": "foo",
  2058  					"bar": "bar",
  2059  				},
  2060  			},
  2061  			New: &Task{
  2062  				Meta: map[string]string{
  2063  					"bar": "bar",
  2064  					"baz": "baz",
  2065  				},
  2066  				Env: map[string]string{
  2067  					"bar": "bar",
  2068  					"baz": "baz",
  2069  				},
  2070  			},
  2071  			Expected: &TaskDiff{
  2072  				Type: DiffTypeEdited,
  2073  				Fields: []*FieldDiff{
  2074  					{
  2075  						Type: DiffTypeAdded,
  2076  						Name: "Env[baz]",
  2077  						Old:  "",
  2078  						New:  "baz",
  2079  					},
  2080  					{
  2081  						Type: DiffTypeDeleted,
  2082  						Name: "Env[foo]",
  2083  						Old:  "foo",
  2084  						New:  "",
  2085  					},
  2086  					{
  2087  						Type: DiffTypeAdded,
  2088  						Name: "Meta[baz]",
  2089  						Old:  "",
  2090  						New:  "baz",
  2091  					},
  2092  					{
  2093  						Type: DiffTypeDeleted,
  2094  						Name: "Meta[foo]",
  2095  						Old:  "foo",
  2096  						New:  "",
  2097  					},
  2098  				},
  2099  			},
  2100  		},
  2101  		{
  2102  			Name: "Constraints edited",
  2103  			Old: &Task{
  2104  				Constraints: []*Constraint{
  2105  					{
  2106  						LTarget: "foo",
  2107  						RTarget: "foo",
  2108  						Operand: "foo",
  2109  						str:     "foo",
  2110  					},
  2111  					{
  2112  						LTarget: "bar",
  2113  						RTarget: "bar",
  2114  						Operand: "bar",
  2115  						str:     "bar",
  2116  					},
  2117  				},
  2118  			},
  2119  			New: &Task{
  2120  				Constraints: []*Constraint{
  2121  					{
  2122  						LTarget: "foo",
  2123  						RTarget: "foo",
  2124  						Operand: "foo",
  2125  						str:     "foo",
  2126  					},
  2127  					{
  2128  						LTarget: "baz",
  2129  						RTarget: "baz",
  2130  						Operand: "baz",
  2131  						str:     "baz",
  2132  					},
  2133  				},
  2134  			},
  2135  			Expected: &TaskDiff{
  2136  				Type: DiffTypeEdited,
  2137  				Objects: []*ObjectDiff{
  2138  					{
  2139  						Type: DiffTypeAdded,
  2140  						Name: "Constraint",
  2141  						Fields: []*FieldDiff{
  2142  							{
  2143  								Type: DiffTypeAdded,
  2144  								Name: "LTarget",
  2145  								Old:  "",
  2146  								New:  "baz",
  2147  							},
  2148  							{
  2149  								Type: DiffTypeAdded,
  2150  								Name: "Operand",
  2151  								Old:  "",
  2152  								New:  "baz",
  2153  							},
  2154  							{
  2155  								Type: DiffTypeAdded,
  2156  								Name: "RTarget",
  2157  								Old:  "",
  2158  								New:  "baz",
  2159  							},
  2160  						},
  2161  					},
  2162  					{
  2163  						Type: DiffTypeDeleted,
  2164  						Name: "Constraint",
  2165  						Fields: []*FieldDiff{
  2166  							{
  2167  								Type: DiffTypeDeleted,
  2168  								Name: "LTarget",
  2169  								Old:  "bar",
  2170  								New:  "",
  2171  							},
  2172  							{
  2173  								Type: DiffTypeDeleted,
  2174  								Name: "Operand",
  2175  								Old:  "bar",
  2176  								New:  "",
  2177  							},
  2178  							{
  2179  								Type: DiffTypeDeleted,
  2180  								Name: "RTarget",
  2181  								Old:  "bar",
  2182  								New:  "",
  2183  							},
  2184  						},
  2185  					},
  2186  				},
  2187  			},
  2188  		},
  2189  		{
  2190  			Name: "LogConfig added",
  2191  			Old:  &Task{},
  2192  			New: &Task{
  2193  				LogConfig: &LogConfig{
  2194  					MaxFiles:      1,
  2195  					MaxFileSizeMB: 10,
  2196  				},
  2197  			},
  2198  			Expected: &TaskDiff{
  2199  				Type: DiffTypeEdited,
  2200  				Objects: []*ObjectDiff{
  2201  					{
  2202  						Type: DiffTypeAdded,
  2203  						Name: "LogConfig",
  2204  						Fields: []*FieldDiff{
  2205  							{
  2206  								Type: DiffTypeAdded,
  2207  								Name: "MaxFileSizeMB",
  2208  								Old:  "",
  2209  								New:  "10",
  2210  							},
  2211  							{
  2212  								Type: DiffTypeAdded,
  2213  								Name: "MaxFiles",
  2214  								Old:  "",
  2215  								New:  "1",
  2216  							},
  2217  						},
  2218  					},
  2219  				},
  2220  			},
  2221  		},
  2222  		{
  2223  			Name: "LogConfig deleted",
  2224  			Old: &Task{
  2225  				LogConfig: &LogConfig{
  2226  					MaxFiles:      1,
  2227  					MaxFileSizeMB: 10,
  2228  				},
  2229  			},
  2230  			New: &Task{},
  2231  			Expected: &TaskDiff{
  2232  				Type: DiffTypeEdited,
  2233  				Objects: []*ObjectDiff{
  2234  					{
  2235  						Type: DiffTypeDeleted,
  2236  						Name: "LogConfig",
  2237  						Fields: []*FieldDiff{
  2238  							{
  2239  								Type: DiffTypeDeleted,
  2240  								Name: "MaxFileSizeMB",
  2241  								Old:  "10",
  2242  								New:  "",
  2243  							},
  2244  							{
  2245  								Type: DiffTypeDeleted,
  2246  								Name: "MaxFiles",
  2247  								Old:  "1",
  2248  								New:  "",
  2249  							},
  2250  						},
  2251  					},
  2252  				},
  2253  			},
  2254  		},
  2255  		{
  2256  			Name: "LogConfig edited",
  2257  			Old: &Task{
  2258  				LogConfig: &LogConfig{
  2259  					MaxFiles:      1,
  2260  					MaxFileSizeMB: 10,
  2261  				},
  2262  			},
  2263  			New: &Task{
  2264  				LogConfig: &LogConfig{
  2265  					MaxFiles:      2,
  2266  					MaxFileSizeMB: 20,
  2267  				},
  2268  			},
  2269  			Expected: &TaskDiff{
  2270  				Type: DiffTypeEdited,
  2271  				Objects: []*ObjectDiff{
  2272  					{
  2273  						Type: DiffTypeEdited,
  2274  						Name: "LogConfig",
  2275  						Fields: []*FieldDiff{
  2276  							{
  2277  								Type: DiffTypeEdited,
  2278  								Name: "MaxFileSizeMB",
  2279  								Old:  "10",
  2280  								New:  "20",
  2281  							},
  2282  							{
  2283  								Type: DiffTypeEdited,
  2284  								Name: "MaxFiles",
  2285  								Old:  "1",
  2286  								New:  "2",
  2287  							},
  2288  						},
  2289  					},
  2290  				},
  2291  			},
  2292  		},
  2293  		{
  2294  			Name:       "LogConfig edited with context",
  2295  			Contextual: true,
  2296  			Old: &Task{
  2297  				LogConfig: &LogConfig{
  2298  					MaxFiles:      1,
  2299  					MaxFileSizeMB: 10,
  2300  				},
  2301  			},
  2302  			New: &Task{
  2303  				LogConfig: &LogConfig{
  2304  					MaxFiles:      1,
  2305  					MaxFileSizeMB: 20,
  2306  				},
  2307  			},
  2308  			Expected: &TaskDiff{
  2309  				Type: DiffTypeEdited,
  2310  				Objects: []*ObjectDiff{
  2311  					{
  2312  						Type: DiffTypeEdited,
  2313  						Name: "LogConfig",
  2314  						Fields: []*FieldDiff{
  2315  							{
  2316  								Type: DiffTypeEdited,
  2317  								Name: "MaxFileSizeMB",
  2318  								Old:  "10",
  2319  								New:  "20",
  2320  							},
  2321  							{
  2322  								Type: DiffTypeNone,
  2323  								Name: "MaxFiles",
  2324  								Old:  "1",
  2325  								New:  "1",
  2326  							},
  2327  						},
  2328  					},
  2329  				},
  2330  			},
  2331  		},
  2332  		{
  2333  			Name: "Artifacts edited",
  2334  			Old: &Task{
  2335  				Artifacts: []*TaskArtifact{
  2336  					{
  2337  						GetterSource: "foo",
  2338  						GetterOptions: map[string]string{
  2339  							"foo": "bar",
  2340  						},
  2341  						RelativeDest: "foo",
  2342  					},
  2343  					{
  2344  						GetterSource: "bar",
  2345  						GetterOptions: map[string]string{
  2346  							"bar": "baz",
  2347  						},
  2348  						RelativeDest: "bar",
  2349  					},
  2350  				},
  2351  			},
  2352  			New: &Task{
  2353  				Artifacts: []*TaskArtifact{
  2354  					{
  2355  						GetterSource: "foo",
  2356  						GetterOptions: map[string]string{
  2357  							"foo": "bar",
  2358  						},
  2359  						RelativeDest: "foo",
  2360  					},
  2361  					{
  2362  						GetterSource: "bam",
  2363  						GetterOptions: map[string]string{
  2364  							"bam": "baz",
  2365  						},
  2366  						RelativeDest: "bam",
  2367  					},
  2368  				},
  2369  			},
  2370  			Expected: &TaskDiff{
  2371  				Type: DiffTypeEdited,
  2372  				Objects: []*ObjectDiff{
  2373  					{
  2374  						Type: DiffTypeAdded,
  2375  						Name: "Artifact",
  2376  						Fields: []*FieldDiff{
  2377  							{
  2378  								Type: DiffTypeAdded,
  2379  								Name: "GetterOptions[bam]",
  2380  								Old:  "",
  2381  								New:  "baz",
  2382  							},
  2383  							{
  2384  								Type: DiffTypeAdded,
  2385  								Name: "GetterSource",
  2386  								Old:  "",
  2387  								New:  "bam",
  2388  							},
  2389  							{
  2390  								Type: DiffTypeAdded,
  2391  								Name: "RelativeDest",
  2392  								Old:  "",
  2393  								New:  "bam",
  2394  							},
  2395  						},
  2396  					},
  2397  					{
  2398  						Type: DiffTypeDeleted,
  2399  						Name: "Artifact",
  2400  						Fields: []*FieldDiff{
  2401  							{
  2402  								Type: DiffTypeDeleted,
  2403  								Name: "GetterOptions[bar]",
  2404  								Old:  "baz",
  2405  								New:  "",
  2406  							},
  2407  							{
  2408  								Type: DiffTypeDeleted,
  2409  								Name: "GetterSource",
  2410  								Old:  "bar",
  2411  								New:  "",
  2412  							},
  2413  							{
  2414  								Type: DiffTypeDeleted,
  2415  								Name: "RelativeDest",
  2416  								Old:  "bar",
  2417  								New:  "",
  2418  							},
  2419  						},
  2420  					},
  2421  				},
  2422  			},
  2423  		},
  2424  		{
  2425  			Name: "Resources edited (no networks)",
  2426  			Old: &Task{
  2427  				Resources: &Resources{
  2428  					CPU:      100,
  2429  					MemoryMB: 100,
  2430  					DiskMB:   100,
  2431  					IOPS:     100,
  2432  				},
  2433  			},
  2434  			New: &Task{
  2435  				Resources: &Resources{
  2436  					CPU:      200,
  2437  					MemoryMB: 200,
  2438  					DiskMB:   200,
  2439  					IOPS:     200,
  2440  				},
  2441  			},
  2442  			Expected: &TaskDiff{
  2443  				Type: DiffTypeEdited,
  2444  				Objects: []*ObjectDiff{
  2445  					{
  2446  						Type: DiffTypeEdited,
  2447  						Name: "Resources",
  2448  						Fields: []*FieldDiff{
  2449  							{
  2450  								Type: DiffTypeEdited,
  2451  								Name: "CPU",
  2452  								Old:  "100",
  2453  								New:  "200",
  2454  							},
  2455  							{
  2456  								Type: DiffTypeEdited,
  2457  								Name: "DiskMB",
  2458  								Old:  "100",
  2459  								New:  "200",
  2460  							},
  2461  							{
  2462  								Type: DiffTypeEdited,
  2463  								Name: "IOPS",
  2464  								Old:  "100",
  2465  								New:  "200",
  2466  							},
  2467  							{
  2468  								Type: DiffTypeEdited,
  2469  								Name: "MemoryMB",
  2470  								Old:  "100",
  2471  								New:  "200",
  2472  							},
  2473  						},
  2474  					},
  2475  				},
  2476  			},
  2477  		},
  2478  		{
  2479  			Name:       "Resources edited (no networks) with context",
  2480  			Contextual: true,
  2481  			Old: &Task{
  2482  				Resources: &Resources{
  2483  					CPU:      100,
  2484  					MemoryMB: 100,
  2485  					DiskMB:   100,
  2486  					IOPS:     100,
  2487  				},
  2488  			},
  2489  			New: &Task{
  2490  				Resources: &Resources{
  2491  					CPU:      200,
  2492  					MemoryMB: 100,
  2493  					DiskMB:   200,
  2494  					IOPS:     100,
  2495  				},
  2496  			},
  2497  			Expected: &TaskDiff{
  2498  				Type: DiffTypeEdited,
  2499  				Objects: []*ObjectDiff{
  2500  					{
  2501  						Type: DiffTypeEdited,
  2502  						Name: "Resources",
  2503  						Fields: []*FieldDiff{
  2504  							{
  2505  								Type: DiffTypeEdited,
  2506  								Name: "CPU",
  2507  								Old:  "100",
  2508  								New:  "200",
  2509  							},
  2510  							{
  2511  								Type: DiffTypeEdited,
  2512  								Name: "DiskMB",
  2513  								Old:  "100",
  2514  								New:  "200",
  2515  							},
  2516  							{
  2517  								Type: DiffTypeNone,
  2518  								Name: "IOPS",
  2519  								Old:  "100",
  2520  								New:  "100",
  2521  							},
  2522  							{
  2523  								Type: DiffTypeNone,
  2524  								Name: "MemoryMB",
  2525  								Old:  "100",
  2526  								New:  "100",
  2527  							},
  2528  						},
  2529  					},
  2530  				},
  2531  			},
  2532  		},
  2533  		{
  2534  			Name: "Network Resources edited",
  2535  			Old: &Task{
  2536  				Resources: &Resources{
  2537  					Networks: []*NetworkResource{
  2538  						{
  2539  							Device: "foo",
  2540  							CIDR:   "foo",
  2541  							IP:     "foo",
  2542  							MBits:  100,
  2543  							ReservedPorts: []Port{
  2544  								{
  2545  									Label: "foo",
  2546  									Value: 80,
  2547  								},
  2548  							},
  2549  							DynamicPorts: []Port{
  2550  								{
  2551  									Label: "bar",
  2552  								},
  2553  							},
  2554  						},
  2555  					},
  2556  				},
  2557  			},
  2558  			New: &Task{
  2559  				Resources: &Resources{
  2560  					Networks: []*NetworkResource{
  2561  						{
  2562  							Device: "bar",
  2563  							CIDR:   "bar",
  2564  							IP:     "bar",
  2565  							MBits:  200,
  2566  							ReservedPorts: []Port{
  2567  								{
  2568  									Label: "foo",
  2569  									Value: 81,
  2570  								},
  2571  							},
  2572  							DynamicPorts: []Port{
  2573  								{
  2574  									Label: "baz",
  2575  								},
  2576  							},
  2577  						},
  2578  					},
  2579  				},
  2580  			},
  2581  			Expected: &TaskDiff{
  2582  				Type: DiffTypeEdited,
  2583  				Objects: []*ObjectDiff{
  2584  					{
  2585  						Type: DiffTypeEdited,
  2586  						Name: "Resources",
  2587  						Objects: []*ObjectDiff{
  2588  							{
  2589  								Type: DiffTypeAdded,
  2590  								Name: "Network",
  2591  								Fields: []*FieldDiff{
  2592  									{
  2593  										Type: DiffTypeAdded,
  2594  										Name: "MBits",
  2595  										Old:  "",
  2596  										New:  "200",
  2597  									},
  2598  								},
  2599  								Objects: []*ObjectDiff{
  2600  									{
  2601  										Type: DiffTypeAdded,
  2602  										Name: "Static Port",
  2603  										Fields: []*FieldDiff{
  2604  											{
  2605  												Type: DiffTypeAdded,
  2606  												Name: "Label",
  2607  												Old:  "",
  2608  												New:  "foo",
  2609  											},
  2610  											{
  2611  												Type: DiffTypeAdded,
  2612  												Name: "Value",
  2613  												Old:  "",
  2614  												New:  "81",
  2615  											},
  2616  										},
  2617  									},
  2618  									{
  2619  										Type: DiffTypeAdded,
  2620  										Name: "Dynamic Port",
  2621  										Fields: []*FieldDiff{
  2622  											{
  2623  												Type: DiffTypeAdded,
  2624  												Name: "Label",
  2625  												Old:  "",
  2626  												New:  "baz",
  2627  											},
  2628  										},
  2629  									},
  2630  								},
  2631  							},
  2632  							{
  2633  								Type: DiffTypeDeleted,
  2634  								Name: "Network",
  2635  								Fields: []*FieldDiff{
  2636  									{
  2637  										Type: DiffTypeDeleted,
  2638  										Name: "MBits",
  2639  										Old:  "100",
  2640  										New:  "",
  2641  									},
  2642  								},
  2643  								Objects: []*ObjectDiff{
  2644  									{
  2645  										Type: DiffTypeDeleted,
  2646  										Name: "Static Port",
  2647  										Fields: []*FieldDiff{
  2648  											{
  2649  												Type: DiffTypeDeleted,
  2650  												Name: "Label",
  2651  												Old:  "foo",
  2652  												New:  "",
  2653  											},
  2654  											{
  2655  												Type: DiffTypeDeleted,
  2656  												Name: "Value",
  2657  												Old:  "80",
  2658  												New:  "",
  2659  											},
  2660  										},
  2661  									},
  2662  									{
  2663  										Type: DiffTypeDeleted,
  2664  										Name: "Dynamic Port",
  2665  										Fields: []*FieldDiff{
  2666  											{
  2667  												Type: DiffTypeDeleted,
  2668  												Name: "Label",
  2669  												Old:  "bar",
  2670  												New:  "",
  2671  											},
  2672  										},
  2673  									},
  2674  								},
  2675  							},
  2676  						},
  2677  					},
  2678  				},
  2679  			},
  2680  		},
  2681  		{
  2682  			Name: "Config same",
  2683  			Old: &Task{
  2684  				Config: map[string]interface{}{
  2685  					"foo": 1,
  2686  					"bar": "bar",
  2687  					"bam": []string{"a", "b"},
  2688  					"baz": map[string]int{
  2689  						"a": 1,
  2690  						"b": 2,
  2691  					},
  2692  					"boom": &Port{
  2693  						Label: "boom_port",
  2694  					},
  2695  				},
  2696  			},
  2697  			New: &Task{
  2698  				Config: map[string]interface{}{
  2699  					"foo": 1,
  2700  					"bar": "bar",
  2701  					"bam": []string{"a", "b"},
  2702  					"baz": map[string]int{
  2703  						"a": 1,
  2704  						"b": 2,
  2705  					},
  2706  					"boom": &Port{
  2707  						Label: "boom_port",
  2708  					},
  2709  				},
  2710  			},
  2711  			Expected: &TaskDiff{
  2712  				Type: DiffTypeNone,
  2713  			},
  2714  		},
  2715  		{
  2716  			Name: "Config edited",
  2717  			Old: &Task{
  2718  				Config: map[string]interface{}{
  2719  					"foo": 1,
  2720  					"bar": "baz",
  2721  					"bam": []string{"a", "b"},
  2722  					"baz": map[string]int{
  2723  						"a": 1,
  2724  						"b": 2,
  2725  					},
  2726  					"boom": &Port{
  2727  						Label: "boom_port",
  2728  					},
  2729  				},
  2730  			},
  2731  			New: &Task{
  2732  				Config: map[string]interface{}{
  2733  					"foo": 2,
  2734  					"bar": "baz",
  2735  					"bam": []string{"a", "c", "d"},
  2736  					"baz": map[string]int{
  2737  						"b": 3,
  2738  						"c": 4,
  2739  					},
  2740  					"boom": &Port{
  2741  						Label: "boom_port2",
  2742  					},
  2743  				},
  2744  			},
  2745  			Expected: &TaskDiff{
  2746  				Type: DiffTypeEdited,
  2747  				Objects: []*ObjectDiff{
  2748  					{
  2749  						Type: DiffTypeEdited,
  2750  						Name: "Config",
  2751  						Fields: []*FieldDiff{
  2752  							{
  2753  								Type: DiffTypeEdited,
  2754  								Name: "bam[1]",
  2755  								Old:  "b",
  2756  								New:  "c",
  2757  							},
  2758  							{
  2759  								Type: DiffTypeAdded,
  2760  								Name: "bam[2]",
  2761  								Old:  "",
  2762  								New:  "d",
  2763  							},
  2764  							{
  2765  								Type: DiffTypeDeleted,
  2766  								Name: "baz[a]",
  2767  								Old:  "1",
  2768  								New:  "",
  2769  							},
  2770  							{
  2771  								Type: DiffTypeEdited,
  2772  								Name: "baz[b]",
  2773  								Old:  "2",
  2774  								New:  "3",
  2775  							},
  2776  							{
  2777  								Type: DiffTypeAdded,
  2778  								Name: "baz[c]",
  2779  								Old:  "",
  2780  								New:  "4",
  2781  							},
  2782  							{
  2783  								Type: DiffTypeEdited,
  2784  								Name: "boom.Label",
  2785  								Old:  "boom_port",
  2786  								New:  "boom_port2",
  2787  							},
  2788  							{
  2789  								Type: DiffTypeEdited,
  2790  								Name: "foo",
  2791  								Old:  "1",
  2792  								New:  "2",
  2793  							},
  2794  						},
  2795  					},
  2796  				},
  2797  			},
  2798  		},
  2799  		{
  2800  			Name:       "Config edited with context",
  2801  			Contextual: true,
  2802  			Old: &Task{
  2803  				Config: map[string]interface{}{
  2804  					"foo": 1,
  2805  					"bar": "baz",
  2806  					"bam": []string{"a", "b"},
  2807  					"baz": map[string]int{
  2808  						"a": 1,
  2809  						"b": 2,
  2810  					},
  2811  					"boom": &Port{
  2812  						Label: "boom_port",
  2813  					},
  2814  				},
  2815  			},
  2816  			New: &Task{
  2817  				Config: map[string]interface{}{
  2818  					"foo": 2,
  2819  					"bar": "baz",
  2820  					"bam": []string{"a", "c", "d"},
  2821  					"baz": map[string]int{
  2822  						"a": 1,
  2823  						"b": 2,
  2824  					},
  2825  					"boom": &Port{
  2826  						Label: "boom_port",
  2827  					},
  2828  				},
  2829  			},
  2830  			Expected: &TaskDiff{
  2831  				Type: DiffTypeEdited,
  2832  				Objects: []*ObjectDiff{
  2833  					{
  2834  						Type: DiffTypeEdited,
  2835  						Name: "Config",
  2836  						Fields: []*FieldDiff{
  2837  							{
  2838  								Type: DiffTypeNone,
  2839  								Name: "bam[0]",
  2840  								Old:  "a",
  2841  								New:  "a",
  2842  							},
  2843  							{
  2844  								Type: DiffTypeEdited,
  2845  								Name: "bam[1]",
  2846  								Old:  "b",
  2847  								New:  "c",
  2848  							},
  2849  							{
  2850  								Type: DiffTypeAdded,
  2851  								Name: "bam[2]",
  2852  								Old:  "",
  2853  								New:  "d",
  2854  							},
  2855  							{
  2856  								Type: DiffTypeNone,
  2857  								Name: "bar",
  2858  								Old:  "baz",
  2859  								New:  "baz",
  2860  							},
  2861  							{
  2862  								Type: DiffTypeNone,
  2863  								Name: "baz[a]",
  2864  								Old:  "1",
  2865  								New:  "1",
  2866  							},
  2867  							{
  2868  								Type: DiffTypeNone,
  2869  								Name: "baz[b]",
  2870  								Old:  "2",
  2871  								New:  "2",
  2872  							},
  2873  							{
  2874  								Type: DiffTypeNone,
  2875  								Name: "boom.Label",
  2876  								Old:  "boom_port",
  2877  								New:  "boom_port",
  2878  							},
  2879  							{
  2880  								Type: DiffTypeNone,
  2881  								Name: "boom.Value",
  2882  								Old:  "0",
  2883  								New:  "0",
  2884  							},
  2885  							{
  2886  								Type: DiffTypeEdited,
  2887  								Name: "foo",
  2888  								Old:  "1",
  2889  								New:  "2",
  2890  							},
  2891  						},
  2892  					},
  2893  				},
  2894  			},
  2895  		},
  2896  		{
  2897  			Name: "Services edited (no checks)",
  2898  			Old: &Task{
  2899  				Services: []*Service{
  2900  					{
  2901  						Name:      "foo",
  2902  						PortLabel: "foo",
  2903  					},
  2904  					{
  2905  						Name:      "bar",
  2906  						PortLabel: "bar",
  2907  					},
  2908  					{
  2909  						Name:      "baz",
  2910  						PortLabel: "baz",
  2911  					},
  2912  				},
  2913  			},
  2914  			New: &Task{
  2915  				Services: []*Service{
  2916  					{
  2917  						Name:      "bar",
  2918  						PortLabel: "bar",
  2919  					},
  2920  					{
  2921  						Name:      "baz",
  2922  						PortLabel: "baz2",
  2923  					},
  2924  					{
  2925  						Name:      "bam",
  2926  						PortLabel: "bam",
  2927  					},
  2928  				},
  2929  			},
  2930  			Expected: &TaskDiff{
  2931  				Type: DiffTypeEdited,
  2932  				Objects: []*ObjectDiff{
  2933  					{
  2934  						Type: DiffTypeEdited,
  2935  						Name: "Service",
  2936  						Fields: []*FieldDiff{
  2937  							{
  2938  								Type: DiffTypeEdited,
  2939  								Name: "PortLabel",
  2940  								Old:  "baz",
  2941  								New:  "baz2",
  2942  							},
  2943  						},
  2944  					},
  2945  					{
  2946  						Type: DiffTypeAdded,
  2947  						Name: "Service",
  2948  						Fields: []*FieldDiff{
  2949  							{
  2950  								Type: DiffTypeAdded,
  2951  								Name: "Name",
  2952  								Old:  "",
  2953  								New:  "bam",
  2954  							},
  2955  							{
  2956  								Type: DiffTypeAdded,
  2957  								Name: "PortLabel",
  2958  								Old:  "",
  2959  								New:  "bam",
  2960  							},
  2961  						},
  2962  					},
  2963  					{
  2964  						Type: DiffTypeDeleted,
  2965  						Name: "Service",
  2966  						Fields: []*FieldDiff{
  2967  							{
  2968  								Type: DiffTypeDeleted,
  2969  								Name: "Name",
  2970  								Old:  "foo",
  2971  								New:  "",
  2972  							},
  2973  							{
  2974  								Type: DiffTypeDeleted,
  2975  								Name: "PortLabel",
  2976  								Old:  "foo",
  2977  								New:  "",
  2978  							},
  2979  						},
  2980  					},
  2981  				},
  2982  			},
  2983  		},
  2984  		{
  2985  			Name:       "Services edited (no checks) with context",
  2986  			Contextual: true,
  2987  			Old: &Task{
  2988  				Services: []*Service{
  2989  					{
  2990  						Name:      "foo",
  2991  						PortLabel: "foo",
  2992  					},
  2993  				},
  2994  			},
  2995  			New: &Task{
  2996  				Services: []*Service{
  2997  					{
  2998  						Name:      "foo",
  2999  						PortLabel: "bar",
  3000  					},
  3001  				},
  3002  			},
  3003  			Expected: &TaskDiff{
  3004  				Type: DiffTypeEdited,
  3005  				Objects: []*ObjectDiff{
  3006  					{
  3007  						Type: DiffTypeEdited,
  3008  						Name: "Service",
  3009  						Fields: []*FieldDiff{
  3010  							{
  3011  								Type: DiffTypeNone,
  3012  								Name: "Name",
  3013  								Old:  "foo",
  3014  								New:  "foo",
  3015  							},
  3016  							{
  3017  								Type: DiffTypeEdited,
  3018  								Name: "PortLabel",
  3019  								Old:  "foo",
  3020  								New:  "bar",
  3021  							},
  3022  						},
  3023  					},
  3024  				},
  3025  			},
  3026  		},
  3027  		{
  3028  			Name: "Service Checks edited",
  3029  			Old: &Task{
  3030  				Services: []*Service{
  3031  					{
  3032  						Name: "foo",
  3033  						Checks: []*ServiceCheck{
  3034  							{
  3035  								Name:     "foo",
  3036  								Type:     "http",
  3037  								Command:  "foo",
  3038  								Args:     []string{"foo"},
  3039  								Path:     "foo",
  3040  								Protocol: "http",
  3041  								Interval: 1 * time.Second,
  3042  								Timeout:  1 * time.Second,
  3043  							},
  3044  							{
  3045  								Name:     "bar",
  3046  								Type:     "http",
  3047  								Command:  "foo",
  3048  								Args:     []string{"foo"},
  3049  								Path:     "foo",
  3050  								Protocol: "http",
  3051  								Interval: 1 * time.Second,
  3052  								Timeout:  1 * time.Second,
  3053  							},
  3054  							{
  3055  								Name:     "baz",
  3056  								Type:     "http",
  3057  								Command:  "foo",
  3058  								Args:     []string{"foo"},
  3059  								Path:     "foo",
  3060  								Protocol: "http",
  3061  								Interval: 1 * time.Second,
  3062  								Timeout:  1 * time.Second,
  3063  							},
  3064  						},
  3065  					},
  3066  				},
  3067  			},
  3068  			New: &Task{
  3069  				Services: []*Service{
  3070  					{
  3071  						Name: "foo",
  3072  						Checks: []*ServiceCheck{
  3073  							{
  3074  								Name:     "bar",
  3075  								Type:     "http",
  3076  								Command:  "foo",
  3077  								Args:     []string{"foo"},
  3078  								Path:     "foo",
  3079  								Protocol: "http",
  3080  								Interval: 1 * time.Second,
  3081  								Timeout:  1 * time.Second,
  3082  							},
  3083  							{
  3084  								Name:     "baz",
  3085  								Type:     "tcp",
  3086  								Command:  "foo",
  3087  								Args:     []string{"foo"},
  3088  								Path:     "foo",
  3089  								Protocol: "http",
  3090  								Interval: 1 * time.Second,
  3091  								Timeout:  1 * time.Second,
  3092  							},
  3093  							{
  3094  								Name:     "bam",
  3095  								Type:     "http",
  3096  								Command:  "foo",
  3097  								Args:     []string{"foo"},
  3098  								Path:     "foo",
  3099  								Protocol: "http",
  3100  								Interval: 1 * time.Second,
  3101  								Timeout:  1 * time.Second,
  3102  							},
  3103  						},
  3104  					},
  3105  				},
  3106  			},
  3107  			Expected: &TaskDiff{
  3108  				Type: DiffTypeEdited,
  3109  				Objects: []*ObjectDiff{
  3110  					{
  3111  						Type: DiffTypeEdited,
  3112  						Name: "Service",
  3113  						Objects: []*ObjectDiff{
  3114  							{
  3115  								Type: DiffTypeEdited,
  3116  								Name: "Check",
  3117  								Fields: []*FieldDiff{
  3118  									{
  3119  										Type: DiffTypeEdited,
  3120  										Name: "Type",
  3121  										Old:  "http",
  3122  										New:  "tcp",
  3123  									},
  3124  								},
  3125  							},
  3126  							{
  3127  								Type: DiffTypeAdded,
  3128  								Name: "Check",
  3129  								Fields: []*FieldDiff{
  3130  									{
  3131  										Type: DiffTypeAdded,
  3132  										Name: "Command",
  3133  										Old:  "",
  3134  										New:  "foo",
  3135  									},
  3136  									{
  3137  										Type: DiffTypeAdded,
  3138  										Name: "Interval",
  3139  										Old:  "",
  3140  										New:  "1000000000",
  3141  									},
  3142  									{
  3143  										Type: DiffTypeAdded,
  3144  										Name: "Name",
  3145  										Old:  "",
  3146  										New:  "bam",
  3147  									},
  3148  									{
  3149  										Type: DiffTypeAdded,
  3150  										Name: "Path",
  3151  										Old:  "",
  3152  										New:  "foo",
  3153  									},
  3154  									{
  3155  										Type: DiffTypeAdded,
  3156  										Name: "Protocol",
  3157  										Old:  "",
  3158  										New:  "http",
  3159  									},
  3160  									{
  3161  										Type: DiffTypeAdded,
  3162  										Name: "TLSSkipVerify",
  3163  										Old:  "",
  3164  										New:  "false",
  3165  									},
  3166  									{
  3167  										Type: DiffTypeAdded,
  3168  										Name: "Timeout",
  3169  										Old:  "",
  3170  										New:  "1000000000",
  3171  									},
  3172  									{
  3173  										Type: DiffTypeAdded,
  3174  										Name: "Type",
  3175  										Old:  "",
  3176  										New:  "http",
  3177  									},
  3178  								},
  3179  							},
  3180  							{
  3181  								Type: DiffTypeDeleted,
  3182  								Name: "Check",
  3183  								Fields: []*FieldDiff{
  3184  									{
  3185  										Type: DiffTypeDeleted,
  3186  										Name: "Command",
  3187  										Old:  "foo",
  3188  										New:  "",
  3189  									},
  3190  									{
  3191  										Type: DiffTypeDeleted,
  3192  										Name: "Interval",
  3193  										Old:  "1000000000",
  3194  										New:  "",
  3195  									},
  3196  									{
  3197  										Type: DiffTypeDeleted,
  3198  										Name: "Name",
  3199  										Old:  "foo",
  3200  										New:  "",
  3201  									},
  3202  									{
  3203  										Type: DiffTypeDeleted,
  3204  										Name: "Path",
  3205  										Old:  "foo",
  3206  										New:  "",
  3207  									},
  3208  									{
  3209  										Type: DiffTypeDeleted,
  3210  										Name: "Protocol",
  3211  										Old:  "http",
  3212  										New:  "",
  3213  									},
  3214  									{
  3215  										Type: DiffTypeDeleted,
  3216  										Name: "TLSSkipVerify",
  3217  										Old:  "false",
  3218  										New:  "",
  3219  									},
  3220  									{
  3221  										Type: DiffTypeDeleted,
  3222  										Name: "Timeout",
  3223  										Old:  "1000000000",
  3224  										New:  "",
  3225  									},
  3226  									{
  3227  										Type: DiffTypeDeleted,
  3228  										Name: "Type",
  3229  										Old:  "http",
  3230  										New:  "",
  3231  									},
  3232  								},
  3233  							},
  3234  						},
  3235  					},
  3236  				},
  3237  			},
  3238  		},
  3239  		{
  3240  			Name:       "Service Checks edited with context",
  3241  			Contextual: true,
  3242  			Old: &Task{
  3243  				Services: []*Service{
  3244  					{
  3245  						Name: "foo",
  3246  						Checks: []*ServiceCheck{
  3247  							{
  3248  								Name:          "foo",
  3249  								Type:          "http",
  3250  								Command:       "foo",
  3251  								Args:          []string{"foo"},
  3252  								Path:          "foo",
  3253  								Protocol:      "http",
  3254  								Interval:      1 * time.Second,
  3255  								Timeout:       1 * time.Second,
  3256  								InitialStatus: "critical",
  3257  							},
  3258  						},
  3259  					},
  3260  				},
  3261  			},
  3262  			New: &Task{
  3263  				Services: []*Service{
  3264  					{
  3265  						Name: "foo",
  3266  						Checks: []*ServiceCheck{
  3267  							{
  3268  								Name:          "foo",
  3269  								Type:          "tcp",
  3270  								Command:       "foo",
  3271  								Args:          []string{"foo"},
  3272  								Path:          "foo",
  3273  								Protocol:      "http",
  3274  								Interval:      1 * time.Second,
  3275  								Timeout:       1 * time.Second,
  3276  								InitialStatus: "passing",
  3277  							},
  3278  						},
  3279  					},
  3280  				},
  3281  			},
  3282  			Expected: &TaskDiff{
  3283  				Type: DiffTypeEdited,
  3284  				Objects: []*ObjectDiff{
  3285  					{
  3286  						Type: DiffTypeEdited,
  3287  						Name: "Service",
  3288  						Fields: []*FieldDiff{
  3289  							{
  3290  								Type: DiffTypeNone,
  3291  								Name: "Name",
  3292  								Old:  "foo",
  3293  								New:  "foo",
  3294  							},
  3295  							{
  3296  								Type: DiffTypeNone,
  3297  								Name: "PortLabel",
  3298  								Old:  "",
  3299  								New:  "",
  3300  							},
  3301  						},
  3302  						Objects: []*ObjectDiff{
  3303  							{
  3304  								Type: DiffTypeEdited,
  3305  								Name: "Check",
  3306  								Fields: []*FieldDiff{
  3307  									{
  3308  										Type: DiffTypeNone,
  3309  										Name: "Command",
  3310  										Old:  "foo",
  3311  										New:  "foo",
  3312  									},
  3313  									{
  3314  										Type: DiffTypeEdited,
  3315  										Name: "InitialStatus",
  3316  										Old:  "critical",
  3317  										New:  "passing",
  3318  									},
  3319  									{
  3320  										Type: DiffTypeNone,
  3321  										Name: "Interval",
  3322  										Old:  "1000000000",
  3323  										New:  "1000000000",
  3324  									},
  3325  									{
  3326  										Type: DiffTypeNone,
  3327  										Name: "Name",
  3328  										Old:  "foo",
  3329  										New:  "foo",
  3330  									},
  3331  									{
  3332  										Type: DiffTypeNone,
  3333  										Name: "Path",
  3334  										Old:  "foo",
  3335  										New:  "foo",
  3336  									},
  3337  									{
  3338  										Type: DiffTypeNone,
  3339  										Name: "PortLabel",
  3340  										Old:  "",
  3341  										New:  "",
  3342  									},
  3343  									{
  3344  										Type: DiffTypeNone,
  3345  										Name: "Protocol",
  3346  										Old:  "http",
  3347  										New:  "http",
  3348  									},
  3349  									{
  3350  										Type: DiffTypeNone,
  3351  										Name: "TLSSkipVerify",
  3352  										Old:  "false",
  3353  										New:  "false",
  3354  									},
  3355  									{
  3356  										Type: DiffTypeNone,
  3357  										Name: "Timeout",
  3358  										Old:  "1000000000",
  3359  										New:  "1000000000",
  3360  									},
  3361  									{
  3362  										Type: DiffTypeEdited,
  3363  										Name: "Type",
  3364  										Old:  "http",
  3365  										New:  "tcp",
  3366  									},
  3367  								},
  3368  							},
  3369  						},
  3370  					},
  3371  				},
  3372  			},
  3373  		},
  3374  		{
  3375  			Name: "Vault added",
  3376  			Old:  &Task{},
  3377  			New: &Task{
  3378  				Vault: &Vault{
  3379  					Policies:     []string{"foo", "bar"},
  3380  					Env:          true,
  3381  					ChangeMode:   "signal",
  3382  					ChangeSignal: "SIGUSR1",
  3383  				},
  3384  			},
  3385  			Expected: &TaskDiff{
  3386  				Type: DiffTypeEdited,
  3387  				Objects: []*ObjectDiff{
  3388  					{
  3389  						Type: DiffTypeAdded,
  3390  						Name: "Vault",
  3391  						Fields: []*FieldDiff{
  3392  							{
  3393  								Type: DiffTypeAdded,
  3394  								Name: "ChangeMode",
  3395  								Old:  "",
  3396  								New:  "signal",
  3397  							},
  3398  							{
  3399  								Type: DiffTypeAdded,
  3400  								Name: "ChangeSignal",
  3401  								Old:  "",
  3402  								New:  "SIGUSR1",
  3403  							},
  3404  							{
  3405  								Type: DiffTypeAdded,
  3406  								Name: "Env",
  3407  								Old:  "",
  3408  								New:  "true",
  3409  							},
  3410  						},
  3411  						Objects: []*ObjectDiff{
  3412  							{
  3413  								Type: DiffTypeAdded,
  3414  								Name: "Policies",
  3415  								Fields: []*FieldDiff{
  3416  									{
  3417  										Type: DiffTypeAdded,
  3418  										Name: "Policies",
  3419  										Old:  "",
  3420  										New:  "bar",
  3421  									},
  3422  									{
  3423  										Type: DiffTypeAdded,
  3424  										Name: "Policies",
  3425  										Old:  "",
  3426  										New:  "foo",
  3427  									},
  3428  								},
  3429  							},
  3430  						},
  3431  					},
  3432  				},
  3433  			},
  3434  		},
  3435  		{
  3436  			Name: "Vault deleted",
  3437  			Old: &Task{
  3438  				Vault: &Vault{
  3439  					Policies:     []string{"foo", "bar"},
  3440  					Env:          true,
  3441  					ChangeMode:   "signal",
  3442  					ChangeSignal: "SIGUSR1",
  3443  				},
  3444  			},
  3445  			New: &Task{},
  3446  			Expected: &TaskDiff{
  3447  				Type: DiffTypeEdited,
  3448  				Objects: []*ObjectDiff{
  3449  					{
  3450  						Type: DiffTypeDeleted,
  3451  						Name: "Vault",
  3452  						Fields: []*FieldDiff{
  3453  							{
  3454  								Type: DiffTypeDeleted,
  3455  								Name: "ChangeMode",
  3456  								Old:  "signal",
  3457  								New:  "",
  3458  							},
  3459  							{
  3460  								Type: DiffTypeDeleted,
  3461  								Name: "ChangeSignal",
  3462  								Old:  "SIGUSR1",
  3463  								New:  "",
  3464  							},
  3465  							{
  3466  								Type: DiffTypeDeleted,
  3467  								Name: "Env",
  3468  								Old:  "true",
  3469  								New:  "",
  3470  							},
  3471  						},
  3472  						Objects: []*ObjectDiff{
  3473  							{
  3474  								Type: DiffTypeDeleted,
  3475  								Name: "Policies",
  3476  								Fields: []*FieldDiff{
  3477  									{
  3478  										Type: DiffTypeDeleted,
  3479  										Name: "Policies",
  3480  										Old:  "bar",
  3481  										New:  "",
  3482  									},
  3483  									{
  3484  										Type: DiffTypeDeleted,
  3485  										Name: "Policies",
  3486  										Old:  "foo",
  3487  										New:  "",
  3488  									},
  3489  								},
  3490  							},
  3491  						},
  3492  					},
  3493  				},
  3494  			},
  3495  		},
  3496  		{
  3497  			Name: "Vault edited",
  3498  			Old: &Task{
  3499  				Vault: &Vault{
  3500  					Policies:     []string{"foo", "bar"},
  3501  					Env:          true,
  3502  					ChangeMode:   "signal",
  3503  					ChangeSignal: "SIGUSR1",
  3504  				},
  3505  			},
  3506  			New: &Task{
  3507  				Vault: &Vault{
  3508  					Policies:     []string{"bar", "baz"},
  3509  					Env:          false,
  3510  					ChangeMode:   "restart",
  3511  					ChangeSignal: "foo",
  3512  				},
  3513  			},
  3514  			Expected: &TaskDiff{
  3515  				Type: DiffTypeEdited,
  3516  				Objects: []*ObjectDiff{
  3517  					{
  3518  						Type: DiffTypeEdited,
  3519  						Name: "Vault",
  3520  						Fields: []*FieldDiff{
  3521  							{
  3522  								Type: DiffTypeEdited,
  3523  								Name: "ChangeMode",
  3524  								Old:  "signal",
  3525  								New:  "restart",
  3526  							},
  3527  							{
  3528  								Type: DiffTypeEdited,
  3529  								Name: "ChangeSignal",
  3530  								Old:  "SIGUSR1",
  3531  								New:  "foo",
  3532  							},
  3533  							{
  3534  								Type: DiffTypeEdited,
  3535  								Name: "Env",
  3536  								Old:  "true",
  3537  								New:  "false",
  3538  							},
  3539  						},
  3540  						Objects: []*ObjectDiff{
  3541  							{
  3542  								Type: DiffTypeEdited,
  3543  								Name: "Policies",
  3544  								Fields: []*FieldDiff{
  3545  									{
  3546  										Type: DiffTypeAdded,
  3547  										Name: "Policies",
  3548  										Old:  "",
  3549  										New:  "baz",
  3550  									},
  3551  									{
  3552  										Type: DiffTypeDeleted,
  3553  										Name: "Policies",
  3554  										Old:  "foo",
  3555  										New:  "",
  3556  									},
  3557  								},
  3558  							},
  3559  						},
  3560  					},
  3561  				},
  3562  			},
  3563  		},
  3564  		{
  3565  			Name:       "Vault edited with context",
  3566  			Contextual: true,
  3567  			Old: &Task{
  3568  				Vault: &Vault{
  3569  					Policies:     []string{"foo", "bar"},
  3570  					Env:          true,
  3571  					ChangeMode:   "signal",
  3572  					ChangeSignal: "SIGUSR1",
  3573  				},
  3574  			},
  3575  			New: &Task{
  3576  				Vault: &Vault{
  3577  					Policies:     []string{"bar", "baz"},
  3578  					Env:          true,
  3579  					ChangeMode:   "signal",
  3580  					ChangeSignal: "SIGUSR1",
  3581  				},
  3582  			},
  3583  			Expected: &TaskDiff{
  3584  				Type: DiffTypeEdited,
  3585  				Objects: []*ObjectDiff{
  3586  					{
  3587  						Type: DiffTypeEdited,
  3588  						Name: "Vault",
  3589  						Fields: []*FieldDiff{
  3590  							{
  3591  								Type: DiffTypeNone,
  3592  								Name: "ChangeMode",
  3593  								Old:  "signal",
  3594  								New:  "signal",
  3595  							},
  3596  							{
  3597  								Type: DiffTypeNone,
  3598  								Name: "ChangeSignal",
  3599  								Old:  "SIGUSR1",
  3600  								New:  "SIGUSR1",
  3601  							},
  3602  							{
  3603  								Type: DiffTypeNone,
  3604  								Name: "Env",
  3605  								Old:  "true",
  3606  								New:  "true",
  3607  							},
  3608  						},
  3609  						Objects: []*ObjectDiff{
  3610  							{
  3611  								Type: DiffTypeEdited,
  3612  								Name: "Policies",
  3613  								Fields: []*FieldDiff{
  3614  									{
  3615  										Type: DiffTypeAdded,
  3616  										Name: "Policies",
  3617  										Old:  "",
  3618  										New:  "baz",
  3619  									},
  3620  									{
  3621  										Type: DiffTypeNone,
  3622  										Name: "Policies",
  3623  										Old:  "bar",
  3624  										New:  "bar",
  3625  									},
  3626  									{
  3627  										Type: DiffTypeDeleted,
  3628  										Name: "Policies",
  3629  										Old:  "foo",
  3630  										New:  "",
  3631  									},
  3632  								},
  3633  							},
  3634  						},
  3635  					},
  3636  				},
  3637  			},
  3638  		},
  3639  		{
  3640  			Name: "Template edited",
  3641  			Old: &Task{
  3642  				Templates: []*Template{
  3643  					{
  3644  						SourcePath:   "foo",
  3645  						DestPath:     "bar",
  3646  						EmbeddedTmpl: "baz",
  3647  						ChangeMode:   "bam",
  3648  						ChangeSignal: "SIGHUP",
  3649  						Splay:        1,
  3650  						Perms:        "0644",
  3651  					},
  3652  					{
  3653  						SourcePath:   "foo2",
  3654  						DestPath:     "bar2",
  3655  						EmbeddedTmpl: "baz2",
  3656  						ChangeMode:   "bam2",
  3657  						ChangeSignal: "SIGHUP2",
  3658  						Splay:        2,
  3659  						Perms:        "0666",
  3660  					},
  3661  				},
  3662  			},
  3663  			New: &Task{
  3664  				Templates: []*Template{
  3665  					{
  3666  						SourcePath:   "foo",
  3667  						DestPath:     "bar",
  3668  						EmbeddedTmpl: "baz",
  3669  						ChangeMode:   "bam",
  3670  						ChangeSignal: "SIGHUP",
  3671  						Splay:        1,
  3672  						Perms:        "0644",
  3673  					},
  3674  					{
  3675  						SourcePath:   "foo3",
  3676  						DestPath:     "bar3",
  3677  						EmbeddedTmpl: "baz3",
  3678  						ChangeMode:   "bam3",
  3679  						ChangeSignal: "SIGHUP3",
  3680  						Splay:        3,
  3681  						Perms:        "0776",
  3682  					},
  3683  				},
  3684  			},
  3685  			Expected: &TaskDiff{
  3686  				Type: DiffTypeEdited,
  3687  				Objects: []*ObjectDiff{
  3688  					{
  3689  						Type: DiffTypeAdded,
  3690  						Name: "Template",
  3691  						Fields: []*FieldDiff{
  3692  							{
  3693  								Type: DiffTypeAdded,
  3694  								Name: "ChangeMode",
  3695  								Old:  "",
  3696  								New:  "bam3",
  3697  							},
  3698  							{
  3699  								Type: DiffTypeAdded,
  3700  								Name: "ChangeSignal",
  3701  								Old:  "",
  3702  								New:  "SIGHUP3",
  3703  							},
  3704  							{
  3705  								Type: DiffTypeAdded,
  3706  								Name: "DestPath",
  3707  								Old:  "",
  3708  								New:  "bar3",
  3709  							},
  3710  							{
  3711  								Type: DiffTypeAdded,
  3712  								Name: "EmbeddedTmpl",
  3713  								Old:  "",
  3714  								New:  "baz3",
  3715  							},
  3716  							{
  3717  								Type: DiffTypeAdded,
  3718  								Name: "Perms",
  3719  								Old:  "",
  3720  								New:  "0776",
  3721  							},
  3722  							{
  3723  								Type: DiffTypeAdded,
  3724  								Name: "SourcePath",
  3725  								Old:  "",
  3726  								New:  "foo3",
  3727  							},
  3728  							{
  3729  								Type: DiffTypeAdded,
  3730  								Name: "Splay",
  3731  								Old:  "",
  3732  								New:  "3",
  3733  							},
  3734  						},
  3735  					},
  3736  					{
  3737  						Type: DiffTypeDeleted,
  3738  						Name: "Template",
  3739  						Fields: []*FieldDiff{
  3740  							{
  3741  								Type: DiffTypeDeleted,
  3742  								Name: "ChangeMode",
  3743  								Old:  "bam2",
  3744  								New:  "",
  3745  							},
  3746  							{
  3747  								Type: DiffTypeDeleted,
  3748  								Name: "ChangeSignal",
  3749  								Old:  "SIGHUP2",
  3750  								New:  "",
  3751  							},
  3752  							{
  3753  								Type: DiffTypeDeleted,
  3754  								Name: "DestPath",
  3755  								Old:  "bar2",
  3756  								New:  "",
  3757  							},
  3758  							{
  3759  								Type: DiffTypeDeleted,
  3760  								Name: "EmbeddedTmpl",
  3761  								Old:  "baz2",
  3762  								New:  "",
  3763  							},
  3764  							{
  3765  								Type: DiffTypeDeleted,
  3766  								Name: "Perms",
  3767  								Old:  "0666",
  3768  								New:  "",
  3769  							},
  3770  							{
  3771  								Type: DiffTypeDeleted,
  3772  								Name: "SourcePath",
  3773  								Old:  "foo2",
  3774  								New:  "",
  3775  							},
  3776  							{
  3777  								Type: DiffTypeDeleted,
  3778  								Name: "Splay",
  3779  								Old:  "2",
  3780  								New:  "",
  3781  							},
  3782  						},
  3783  					},
  3784  				},
  3785  			},
  3786  		},
  3787  		{
  3788  			Name: "DispatchPayload added",
  3789  			Old:  &Task{},
  3790  			New: &Task{
  3791  				DispatchPayload: &DispatchPayloadConfig{
  3792  					File: "foo",
  3793  				},
  3794  			},
  3795  			Expected: &TaskDiff{
  3796  				Type: DiffTypeEdited,
  3797  				Objects: []*ObjectDiff{
  3798  					{
  3799  						Type: DiffTypeAdded,
  3800  						Name: "DispatchPayload",
  3801  						Fields: []*FieldDiff{
  3802  							{
  3803  								Type: DiffTypeAdded,
  3804  								Name: "File",
  3805  								Old:  "",
  3806  								New:  "foo",
  3807  							},
  3808  						},
  3809  					},
  3810  				},
  3811  			},
  3812  		},
  3813  		{
  3814  			Name: "DispatchPayload deleted",
  3815  			Old: &Task{
  3816  				DispatchPayload: &DispatchPayloadConfig{
  3817  					File: "foo",
  3818  				},
  3819  			},
  3820  			New: &Task{},
  3821  			Expected: &TaskDiff{
  3822  				Type: DiffTypeEdited,
  3823  				Objects: []*ObjectDiff{
  3824  					{
  3825  						Type: DiffTypeDeleted,
  3826  						Name: "DispatchPayload",
  3827  						Fields: []*FieldDiff{
  3828  							{
  3829  								Type: DiffTypeDeleted,
  3830  								Name: "File",
  3831  								Old:  "foo",
  3832  								New:  "",
  3833  							},
  3834  						},
  3835  					},
  3836  				},
  3837  			},
  3838  		},
  3839  		{
  3840  			Name: "Dispatch payload edited",
  3841  			Old: &Task{
  3842  				DispatchPayload: &DispatchPayloadConfig{
  3843  					File: "foo",
  3844  				},
  3845  			},
  3846  			New: &Task{
  3847  				DispatchPayload: &DispatchPayloadConfig{
  3848  					File: "bar",
  3849  				},
  3850  			},
  3851  			Expected: &TaskDiff{
  3852  				Type: DiffTypeEdited,
  3853  				Objects: []*ObjectDiff{
  3854  					{
  3855  						Type: DiffTypeEdited,
  3856  						Name: "DispatchPayload",
  3857  						Fields: []*FieldDiff{
  3858  							{
  3859  								Type: DiffTypeEdited,
  3860  								Name: "File",
  3861  								Old:  "foo",
  3862  								New:  "bar",
  3863  							},
  3864  						},
  3865  					},
  3866  				},
  3867  			},
  3868  		},
  3869  		{
  3870  			// Place holder for if more fields are added
  3871  			Name:       "DispatchPayload edited with context",
  3872  			Contextual: true,
  3873  			Old: &Task{
  3874  				DispatchPayload: &DispatchPayloadConfig{
  3875  					File: "foo",
  3876  				},
  3877  			},
  3878  			New: &Task{
  3879  				DispatchPayload: &DispatchPayloadConfig{
  3880  					File: "bar",
  3881  				},
  3882  			},
  3883  			Expected: &TaskDiff{
  3884  				Type: DiffTypeEdited,
  3885  				Objects: []*ObjectDiff{
  3886  					{
  3887  						Type: DiffTypeEdited,
  3888  						Name: "DispatchPayload",
  3889  						Fields: []*FieldDiff{
  3890  							{
  3891  								Type: DiffTypeEdited,
  3892  								Name: "File",
  3893  								Old:  "foo",
  3894  								New:  "bar",
  3895  							},
  3896  						},
  3897  					},
  3898  				},
  3899  			},
  3900  		},
  3901  	}
  3902  
  3903  	for i, c := range cases {
  3904  		t.Run(c.Name, func(t *testing.T) {
  3905  			actual, err := c.Old.Diff(c.New, c.Contextual)
  3906  			if c.Error && err == nil {
  3907  				t.Fatalf("case %d: expected errored", i+1)
  3908  			} else if err != nil {
  3909  				if !c.Error {
  3910  					t.Fatalf("case %d: errored %#v", i+1, err)
  3911  				} else {
  3912  					return
  3913  				}
  3914  			}
  3915  
  3916  			if !reflect.DeepEqual(actual, c.Expected) {
  3917  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  3918  					i+1, actual, c.Expected)
  3919  			}
  3920  		})
  3921  	}
  3922  }