github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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: "Dispatched",
   156  						Old:  "false",
   157  						New:  "",
   158  					},
   159  					{
   160  						Type: DiffTypeDeleted,
   161  						Name: "Meta[foo]",
   162  						Old:  "bar",
   163  						New:  "",
   164  					},
   165  					{
   166  						Type: DiffTypeDeleted,
   167  						Name: "Name",
   168  						Old:  "foo",
   169  						New:  "",
   170  					},
   171  					{
   172  						Type: DiffTypeDeleted,
   173  						Name: "Priority",
   174  						Old:  "10",
   175  						New:  "",
   176  					},
   177  					{
   178  						Type: DiffTypeDeleted,
   179  						Name: "Region",
   180  						Old:  "foo",
   181  						New:  "",
   182  					},
   183  					{
   184  						Type: DiffTypeDeleted,
   185  						Name: "Stop",
   186  						Old:  "false",
   187  						New:  "",
   188  					},
   189  					{
   190  						Type: DiffTypeDeleted,
   191  						Name: "Type",
   192  						Old:  "batch",
   193  						New:  "",
   194  					},
   195  				},
   196  			},
   197  		},
   198  		{
   199  			// Primitive only added job
   200  			Old: nil,
   201  			New: &Job{
   202  				Region:    "foo",
   203  				ID:        "foo",
   204  				Name:      "foo",
   205  				Type:      "batch",
   206  				Priority:  10,
   207  				AllAtOnce: true,
   208  				Meta: map[string]string{
   209  					"foo": "bar",
   210  				},
   211  			},
   212  			Expected: &JobDiff{
   213  				Type: DiffTypeAdded,
   214  				ID:   "foo",
   215  				Fields: []*FieldDiff{
   216  					{
   217  						Type: DiffTypeAdded,
   218  						Name: "AllAtOnce",
   219  						Old:  "",
   220  						New:  "true",
   221  					},
   222  					{
   223  						Type: DiffTypeAdded,
   224  						Name: "Dispatched",
   225  						Old:  "",
   226  						New:  "false",
   227  					},
   228  					{
   229  						Type: DiffTypeAdded,
   230  						Name: "Meta[foo]",
   231  						Old:  "",
   232  						New:  "bar",
   233  					},
   234  					{
   235  						Type: DiffTypeAdded,
   236  						Name: "Name",
   237  						Old:  "",
   238  						New:  "foo",
   239  					},
   240  					{
   241  						Type: DiffTypeAdded,
   242  						Name: "Priority",
   243  						Old:  "",
   244  						New:  "10",
   245  					},
   246  					{
   247  						Type: DiffTypeAdded,
   248  						Name: "Region",
   249  						Old:  "",
   250  						New:  "foo",
   251  					},
   252  					{
   253  						Type: DiffTypeAdded,
   254  						Name: "Stop",
   255  						Old:  "",
   256  						New:  "false",
   257  					},
   258  					{
   259  						Type: DiffTypeAdded,
   260  						Name: "Type",
   261  						Old:  "",
   262  						New:  "batch",
   263  					},
   264  				},
   265  			},
   266  		},
   267  		{
   268  			// Map diff
   269  			Old: &Job{
   270  				Meta: map[string]string{
   271  					"foo": "foo",
   272  					"bar": "bar",
   273  				},
   274  			},
   275  			New: &Job{
   276  				Meta: map[string]string{
   277  					"bar": "bar",
   278  					"baz": "baz",
   279  				},
   280  			},
   281  			Expected: &JobDiff{
   282  				Type: DiffTypeEdited,
   283  				Fields: []*FieldDiff{
   284  					{
   285  						Type: DiffTypeAdded,
   286  						Name: "Meta[baz]",
   287  						Old:  "",
   288  						New:  "baz",
   289  					},
   290  					{
   291  						Type: DiffTypeDeleted,
   292  						Name: "Meta[foo]",
   293  						Old:  "foo",
   294  						New:  "",
   295  					},
   296  				},
   297  			},
   298  		},
   299  		{
   300  			// Datacenter diff both added and removed
   301  			Old: &Job{
   302  				Datacenters: []string{"foo", "bar"},
   303  			},
   304  			New: &Job{
   305  				Datacenters: []string{"baz", "bar"},
   306  			},
   307  			Expected: &JobDiff{
   308  				Type: DiffTypeEdited,
   309  				Objects: []*ObjectDiff{
   310  					{
   311  						Type: DiffTypeEdited,
   312  						Name: "Datacenters",
   313  						Fields: []*FieldDiff{
   314  							{
   315  								Type: DiffTypeAdded,
   316  								Name: "Datacenters",
   317  								Old:  "",
   318  								New:  "baz",
   319  							},
   320  							{
   321  								Type: DiffTypeDeleted,
   322  								Name: "Datacenters",
   323  								Old:  "foo",
   324  								New:  "",
   325  							},
   326  						},
   327  					},
   328  				},
   329  			},
   330  		},
   331  		{
   332  			// Datacenter diff just added
   333  			Old: &Job{
   334  				Datacenters: []string{"foo", "bar"},
   335  			},
   336  			New: &Job{
   337  				Datacenters: []string{"foo", "bar", "baz"},
   338  			},
   339  			Expected: &JobDiff{
   340  				Type: DiffTypeEdited,
   341  				Objects: []*ObjectDiff{
   342  					{
   343  						Type: DiffTypeAdded,
   344  						Name: "Datacenters",
   345  						Fields: []*FieldDiff{
   346  							{
   347  								Type: DiffTypeAdded,
   348  								Name: "Datacenters",
   349  								Old:  "",
   350  								New:  "baz",
   351  							},
   352  						},
   353  					},
   354  				},
   355  			},
   356  		},
   357  		{
   358  			// Datacenter diff just deleted
   359  			Old: &Job{
   360  				Datacenters: []string{"foo", "bar"},
   361  			},
   362  			New: &Job{
   363  				Datacenters: []string{"foo"},
   364  			},
   365  			Expected: &JobDiff{
   366  				Type: DiffTypeEdited,
   367  				Objects: []*ObjectDiff{
   368  					{
   369  						Type: DiffTypeDeleted,
   370  						Name: "Datacenters",
   371  						Fields: []*FieldDiff{
   372  							{
   373  								Type: DiffTypeDeleted,
   374  								Name: "Datacenters",
   375  								Old:  "bar",
   376  								New:  "",
   377  							},
   378  						},
   379  					},
   380  				},
   381  			},
   382  		},
   383  		{
   384  			// Datacenter contextual no change
   385  			Contextual: true,
   386  			Old: &Job{
   387  				Datacenters: []string{"foo", "bar"},
   388  			},
   389  			New: &Job{
   390  				Datacenters: []string{"foo", "bar"},
   391  			},
   392  			Expected: &JobDiff{
   393  				Type: DiffTypeNone,
   394  			},
   395  		},
   396  		{
   397  			// Datacenter contextual
   398  			Contextual: true,
   399  			Old: &Job{
   400  				Datacenters: []string{"foo", "bar"},
   401  			},
   402  			New: &Job{
   403  				Datacenters: []string{"foo", "bar", "baz"},
   404  			},
   405  			Expected: &JobDiff{
   406  				Type: DiffTypeEdited,
   407  				Objects: []*ObjectDiff{
   408  					{
   409  						Type: DiffTypeAdded,
   410  						Name: "Datacenters",
   411  						Fields: []*FieldDiff{
   412  							{
   413  								Type: DiffTypeAdded,
   414  								Name: "Datacenters",
   415  								Old:  "",
   416  								New:  "baz",
   417  							},
   418  							{
   419  								Type: DiffTypeNone,
   420  								Name: "Datacenters",
   421  								Old:  "bar",
   422  								New:  "bar",
   423  							},
   424  							{
   425  								Type: DiffTypeNone,
   426  								Name: "Datacenters",
   427  								Old:  "foo",
   428  								New:  "foo",
   429  							},
   430  						},
   431  					},
   432  				},
   433  			},
   434  		},
   435  		{
   436  			// Periodic added
   437  			Old: &Job{},
   438  			New: &Job{
   439  				Periodic: &PeriodicConfig{
   440  					Enabled:         false,
   441  					Spec:            "*/15 * * * * *",
   442  					SpecType:        "foo",
   443  					ProhibitOverlap: false,
   444  					TimeZone:        "Europe/Minsk",
   445  				},
   446  			},
   447  			Expected: &JobDiff{
   448  				Type: DiffTypeEdited,
   449  				Objects: []*ObjectDiff{
   450  					{
   451  						Type: DiffTypeAdded,
   452  						Name: "Periodic",
   453  						Fields: []*FieldDiff{
   454  							{
   455  								Type: DiffTypeAdded,
   456  								Name: "Enabled",
   457  								Old:  "",
   458  								New:  "false",
   459  							},
   460  							{
   461  								Type: DiffTypeAdded,
   462  								Name: "ProhibitOverlap",
   463  								Old:  "",
   464  								New:  "false",
   465  							},
   466  							{
   467  								Type: DiffTypeAdded,
   468  								Name: "Spec",
   469  								Old:  "",
   470  								New:  "*/15 * * * * *",
   471  							},
   472  							{
   473  								Type: DiffTypeAdded,
   474  								Name: "SpecType",
   475  								Old:  "",
   476  								New:  "foo",
   477  							},
   478  							{
   479  								Type: DiffTypeAdded,
   480  								Name: "TimeZone",
   481  								Old:  "",
   482  								New:  "Europe/Minsk",
   483  							},
   484  						},
   485  					},
   486  				},
   487  			},
   488  		},
   489  		{
   490  			// Periodic deleted
   491  			Old: &Job{
   492  				Periodic: &PeriodicConfig{
   493  					Enabled:         false,
   494  					Spec:            "*/15 * * * * *",
   495  					SpecType:        "foo",
   496  					ProhibitOverlap: false,
   497  					TimeZone:        "Europe/Minsk",
   498  				},
   499  			},
   500  			New: &Job{},
   501  			Expected: &JobDiff{
   502  				Type: DiffTypeEdited,
   503  				Objects: []*ObjectDiff{
   504  					{
   505  						Type: DiffTypeDeleted,
   506  						Name: "Periodic",
   507  						Fields: []*FieldDiff{
   508  							{
   509  								Type: DiffTypeDeleted,
   510  								Name: "Enabled",
   511  								Old:  "false",
   512  								New:  "",
   513  							},
   514  							{
   515  								Type: DiffTypeDeleted,
   516  								Name: "ProhibitOverlap",
   517  								Old:  "false",
   518  								New:  "",
   519  							},
   520  							{
   521  								Type: DiffTypeDeleted,
   522  								Name: "Spec",
   523  								Old:  "*/15 * * * * *",
   524  								New:  "",
   525  							},
   526  							{
   527  								Type: DiffTypeDeleted,
   528  								Name: "SpecType",
   529  								Old:  "foo",
   530  								New:  "",
   531  							},
   532  							{
   533  								Type: DiffTypeDeleted,
   534  								Name: "TimeZone",
   535  								Old:  "Europe/Minsk",
   536  								New:  "",
   537  							},
   538  						},
   539  					},
   540  				},
   541  			},
   542  		},
   543  		{
   544  			// Periodic edited
   545  			Old: &Job{
   546  				Periodic: &PeriodicConfig{
   547  					Enabled:         false,
   548  					Spec:            "*/15 * * * * *",
   549  					SpecType:        "foo",
   550  					ProhibitOverlap: false,
   551  					TimeZone:        "Europe/Minsk",
   552  				},
   553  			},
   554  			New: &Job{
   555  				Periodic: &PeriodicConfig{
   556  					Enabled:         true,
   557  					Spec:            "* * * * * *",
   558  					SpecType:        "cron",
   559  					ProhibitOverlap: true,
   560  					TimeZone:        "America/Los_Angeles",
   561  				},
   562  			},
   563  			Expected: &JobDiff{
   564  				Type: DiffTypeEdited,
   565  				Objects: []*ObjectDiff{
   566  					{
   567  						Type: DiffTypeEdited,
   568  						Name: "Periodic",
   569  						Fields: []*FieldDiff{
   570  							{
   571  								Type: DiffTypeEdited,
   572  								Name: "Enabled",
   573  								Old:  "false",
   574  								New:  "true",
   575  							},
   576  							{
   577  								Type: DiffTypeEdited,
   578  								Name: "ProhibitOverlap",
   579  								Old:  "false",
   580  								New:  "true",
   581  							},
   582  							{
   583  								Type: DiffTypeEdited,
   584  								Name: "Spec",
   585  								Old:  "*/15 * * * * *",
   586  								New:  "* * * * * *",
   587  							},
   588  							{
   589  								Type: DiffTypeEdited,
   590  								Name: "SpecType",
   591  								Old:  "foo",
   592  								New:  "cron",
   593  							},
   594  							{
   595  								Type: DiffTypeEdited,
   596  								Name: "TimeZone",
   597  								Old:  "Europe/Minsk",
   598  								New:  "America/Los_Angeles",
   599  							},
   600  						},
   601  					},
   602  				},
   603  			},
   604  		},
   605  		{
   606  			// Periodic edited with context
   607  			Contextual: true,
   608  			Old: &Job{
   609  				Periodic: &PeriodicConfig{
   610  					Enabled:         false,
   611  					Spec:            "*/15 * * * * *",
   612  					SpecType:        "foo",
   613  					ProhibitOverlap: false,
   614  					TimeZone:        "Europe/Minsk",
   615  				},
   616  			},
   617  			New: &Job{
   618  				Periodic: &PeriodicConfig{
   619  					Enabled:         true,
   620  					Spec:            "* * * * * *",
   621  					SpecType:        "foo",
   622  					ProhibitOverlap: false,
   623  					TimeZone:        "Europe/Minsk",
   624  				},
   625  			},
   626  			Expected: &JobDiff{
   627  				Type: DiffTypeEdited,
   628  				Objects: []*ObjectDiff{
   629  					{
   630  						Type: DiffTypeEdited,
   631  						Name: "Periodic",
   632  						Fields: []*FieldDiff{
   633  							{
   634  								Type: DiffTypeEdited,
   635  								Name: "Enabled",
   636  								Old:  "false",
   637  								New:  "true",
   638  							},
   639  							{
   640  								Type: DiffTypeNone,
   641  								Name: "ProhibitOverlap",
   642  								Old:  "false",
   643  								New:  "false",
   644  							},
   645  							{
   646  								Type: DiffTypeEdited,
   647  								Name: "Spec",
   648  								Old:  "*/15 * * * * *",
   649  								New:  "* * * * * *",
   650  							},
   651  							{
   652  								Type: DiffTypeNone,
   653  								Name: "SpecType",
   654  								Old:  "foo",
   655  								New:  "foo",
   656  							},
   657  							{
   658  								Type: DiffTypeNone,
   659  								Name: "TimeZone",
   660  								Old:  "Europe/Minsk",
   661  								New:  "Europe/Minsk",
   662  							},
   663  						},
   664  					},
   665  				},
   666  			},
   667  		},
   668  		{
   669  			// Constraints edited
   670  			Old: &Job{
   671  				Constraints: []*Constraint{
   672  					{
   673  						LTarget: "foo",
   674  						RTarget: "foo",
   675  						Operand: "foo",
   676  						str:     "foo",
   677  					},
   678  					{
   679  						LTarget: "bar",
   680  						RTarget: "bar",
   681  						Operand: "bar",
   682  						str:     "bar",
   683  					},
   684  				},
   685  			},
   686  			New: &Job{
   687  				Constraints: []*Constraint{
   688  					{
   689  						LTarget: "foo",
   690  						RTarget: "foo",
   691  						Operand: "foo",
   692  						str:     "foo",
   693  					},
   694  					{
   695  						LTarget: "baz",
   696  						RTarget: "baz",
   697  						Operand: "baz",
   698  						str:     "baz",
   699  					},
   700  				},
   701  			},
   702  			Expected: &JobDiff{
   703  				Type: DiffTypeEdited,
   704  				Objects: []*ObjectDiff{
   705  					{
   706  						Type: DiffTypeAdded,
   707  						Name: "Constraint",
   708  						Fields: []*FieldDiff{
   709  							{
   710  								Type: DiffTypeAdded,
   711  								Name: "LTarget",
   712  								Old:  "",
   713  								New:  "baz",
   714  							},
   715  							{
   716  								Type: DiffTypeAdded,
   717  								Name: "Operand",
   718  								Old:  "",
   719  								New:  "baz",
   720  							},
   721  							{
   722  								Type: DiffTypeAdded,
   723  								Name: "RTarget",
   724  								Old:  "",
   725  								New:  "baz",
   726  							},
   727  						},
   728  					},
   729  					{
   730  						Type: DiffTypeDeleted,
   731  						Name: "Constraint",
   732  						Fields: []*FieldDiff{
   733  							{
   734  								Type: DiffTypeDeleted,
   735  								Name: "LTarget",
   736  								Old:  "bar",
   737  								New:  "",
   738  							},
   739  							{
   740  								Type: DiffTypeDeleted,
   741  								Name: "Operand",
   742  								Old:  "bar",
   743  								New:  "",
   744  							},
   745  							{
   746  								Type: DiffTypeDeleted,
   747  								Name: "RTarget",
   748  								Old:  "bar",
   749  								New:  "",
   750  							},
   751  						},
   752  					},
   753  				},
   754  			},
   755  		},
   756  		{
   757  			// Affinities edited
   758  			Old: &Job{
   759  				Affinities: []*Affinity{
   760  					{
   761  						LTarget: "foo",
   762  						RTarget: "foo",
   763  						Operand: "foo",
   764  						Weight:  20,
   765  						str:     "foo",
   766  					},
   767  					{
   768  						LTarget: "bar",
   769  						RTarget: "bar",
   770  						Operand: "bar",
   771  						Weight:  20,
   772  						str:     "bar",
   773  					},
   774  				},
   775  			},
   776  			New: &Job{
   777  				Affinities: []*Affinity{
   778  					{
   779  						LTarget: "foo",
   780  						RTarget: "foo",
   781  						Operand: "foo",
   782  						Weight:  20,
   783  						str:     "foo",
   784  					},
   785  					{
   786  						LTarget: "baz",
   787  						RTarget: "baz",
   788  						Operand: "baz",
   789  						Weight:  20,
   790  						str:     "baz",
   791  					},
   792  				},
   793  			},
   794  			Expected: &JobDiff{
   795  				Type: DiffTypeEdited,
   796  				Objects: []*ObjectDiff{
   797  					{
   798  						Type: DiffTypeAdded,
   799  						Name: "Affinity",
   800  						Fields: []*FieldDiff{
   801  							{
   802  								Type: DiffTypeAdded,
   803  								Name: "LTarget",
   804  								Old:  "",
   805  								New:  "baz",
   806  							},
   807  							{
   808  								Type: DiffTypeAdded,
   809  								Name: "Operand",
   810  								Old:  "",
   811  								New:  "baz",
   812  							},
   813  							{
   814  								Type: DiffTypeAdded,
   815  								Name: "RTarget",
   816  								Old:  "",
   817  								New:  "baz",
   818  							},
   819  							{
   820  								Type: DiffTypeAdded,
   821  								Name: "Weight",
   822  								Old:  "",
   823  								New:  "20",
   824  							},
   825  						},
   826  					},
   827  					{
   828  						Type: DiffTypeDeleted,
   829  						Name: "Affinity",
   830  						Fields: []*FieldDiff{
   831  							{
   832  								Type: DiffTypeDeleted,
   833  								Name: "LTarget",
   834  								Old:  "bar",
   835  								New:  "",
   836  							},
   837  							{
   838  								Type: DiffTypeDeleted,
   839  								Name: "Operand",
   840  								Old:  "bar",
   841  								New:  "",
   842  							},
   843  							{
   844  								Type: DiffTypeDeleted,
   845  								Name: "RTarget",
   846  								Old:  "bar",
   847  								New:  "",
   848  							},
   849  							{
   850  								Type: DiffTypeDeleted,
   851  								Name: "Weight",
   852  								Old:  "20",
   853  								New:  "",
   854  							},
   855  						},
   856  					},
   857  				},
   858  			},
   859  		},
   860  		{
   861  			// Task groups edited
   862  			Old: &Job{
   863  				TaskGroups: []*TaskGroup{
   864  					{
   865  						Name:  "foo",
   866  						Count: 1,
   867  					},
   868  					{
   869  						Name:  "bar",
   870  						Count: 1,
   871  					},
   872  					{
   873  						Name:  "baz",
   874  						Count: 1,
   875  					},
   876  				},
   877  			},
   878  			New: &Job{
   879  				TaskGroups: []*TaskGroup{
   880  					{
   881  						Name:  "bar",
   882  						Count: 1,
   883  					},
   884  					{
   885  						Name:  "baz",
   886  						Count: 2,
   887  					},
   888  					{
   889  						Name:  "bam",
   890  						Count: 1,
   891  					},
   892  				},
   893  			},
   894  			Expected: &JobDiff{
   895  				Type: DiffTypeEdited,
   896  				TaskGroups: []*TaskGroupDiff{
   897  					{
   898  						Type: DiffTypeAdded,
   899  						Name: "bam",
   900  						Fields: []*FieldDiff{
   901  							{
   902  								Type: DiffTypeAdded,
   903  								Name: "Count",
   904  								Old:  "",
   905  								New:  "1",
   906  							},
   907  						},
   908  					},
   909  					{
   910  						Type: DiffTypeNone,
   911  						Name: "bar",
   912  					},
   913  					{
   914  						Type: DiffTypeEdited,
   915  						Name: "baz",
   916  						Fields: []*FieldDiff{
   917  							{
   918  								Type: DiffTypeEdited,
   919  								Name: "Count",
   920  								Old:  "1",
   921  								New:  "2",
   922  							},
   923  						},
   924  					},
   925  					{
   926  						Type: DiffTypeDeleted,
   927  						Name: "foo",
   928  						Fields: []*FieldDiff{
   929  							{
   930  								Type: DiffTypeDeleted,
   931  								Name: "Count",
   932  								Old:  "1",
   933  								New:  "",
   934  							},
   935  						},
   936  					},
   937  				},
   938  			},
   939  		},
   940  		{
   941  			// Parameterized Job added
   942  			Old: &Job{},
   943  			New: &Job{
   944  				ParameterizedJob: &ParameterizedJobConfig{
   945  					Payload:      DispatchPayloadRequired,
   946  					MetaOptional: []string{"foo"},
   947  					MetaRequired: []string{"bar"},
   948  				},
   949  			},
   950  			Expected: &JobDiff{
   951  				Type: DiffTypeEdited,
   952  				Objects: []*ObjectDiff{
   953  					{
   954  						Type: DiffTypeAdded,
   955  						Name: "ParameterizedJob",
   956  						Fields: []*FieldDiff{
   957  							{
   958  								Type: DiffTypeAdded,
   959  								Name: "Payload",
   960  								Old:  "",
   961  								New:  DispatchPayloadRequired,
   962  							},
   963  						},
   964  						Objects: []*ObjectDiff{
   965  							{
   966  								Type: DiffTypeAdded,
   967  								Name: "MetaOptional",
   968  								Fields: []*FieldDiff{
   969  									{
   970  										Type: DiffTypeAdded,
   971  										Name: "MetaOptional",
   972  										Old:  "",
   973  										New:  "foo",
   974  									},
   975  								},
   976  							},
   977  							{
   978  								Type: DiffTypeAdded,
   979  								Name: "MetaRequired",
   980  								Fields: []*FieldDiff{
   981  									{
   982  										Type: DiffTypeAdded,
   983  										Name: "MetaRequired",
   984  										Old:  "",
   985  										New:  "bar",
   986  									},
   987  								},
   988  							},
   989  						},
   990  					},
   991  				},
   992  			},
   993  		},
   994  		{
   995  			// Parameterized Job deleted
   996  			Old: &Job{
   997  				ParameterizedJob: &ParameterizedJobConfig{
   998  					Payload:      DispatchPayloadRequired,
   999  					MetaOptional: []string{"foo"},
  1000  					MetaRequired: []string{"bar"},
  1001  				},
  1002  			},
  1003  			New: &Job{},
  1004  			Expected: &JobDiff{
  1005  				Type: DiffTypeEdited,
  1006  				Objects: []*ObjectDiff{
  1007  					{
  1008  						Type: DiffTypeDeleted,
  1009  						Name: "ParameterizedJob",
  1010  						Fields: []*FieldDiff{
  1011  							{
  1012  								Type: DiffTypeDeleted,
  1013  								Name: "Payload",
  1014  								Old:  DispatchPayloadRequired,
  1015  								New:  "",
  1016  							},
  1017  						},
  1018  						Objects: []*ObjectDiff{
  1019  							{
  1020  								Type: DiffTypeDeleted,
  1021  								Name: "MetaOptional",
  1022  								Fields: []*FieldDiff{
  1023  									{
  1024  										Type: DiffTypeDeleted,
  1025  										Name: "MetaOptional",
  1026  										Old:  "foo",
  1027  										New:  "",
  1028  									},
  1029  								},
  1030  							},
  1031  							{
  1032  								Type: DiffTypeDeleted,
  1033  								Name: "MetaRequired",
  1034  								Fields: []*FieldDiff{
  1035  									{
  1036  										Type: DiffTypeDeleted,
  1037  										Name: "MetaRequired",
  1038  										Old:  "bar",
  1039  										New:  "",
  1040  									},
  1041  								},
  1042  							},
  1043  						},
  1044  					},
  1045  				},
  1046  			},
  1047  		},
  1048  		{
  1049  			// Parameterized Job edited
  1050  			Old: &Job{
  1051  				ParameterizedJob: &ParameterizedJobConfig{
  1052  					Payload:      DispatchPayloadRequired,
  1053  					MetaOptional: []string{"foo"},
  1054  					MetaRequired: []string{"bar"},
  1055  				},
  1056  			},
  1057  			New: &Job{
  1058  				ParameterizedJob: &ParameterizedJobConfig{
  1059  					Payload:      DispatchPayloadOptional,
  1060  					MetaOptional: []string{"bam"},
  1061  					MetaRequired: []string{"bang"},
  1062  				},
  1063  			},
  1064  			Expected: &JobDiff{
  1065  				Type: DiffTypeEdited,
  1066  				Objects: []*ObjectDiff{
  1067  					{
  1068  						Type: DiffTypeEdited,
  1069  						Name: "ParameterizedJob",
  1070  						Fields: []*FieldDiff{
  1071  							{
  1072  								Type: DiffTypeEdited,
  1073  								Name: "Payload",
  1074  								Old:  DispatchPayloadRequired,
  1075  								New:  DispatchPayloadOptional,
  1076  							},
  1077  						},
  1078  						Objects: []*ObjectDiff{
  1079  							{
  1080  								Type: DiffTypeEdited,
  1081  								Name: "MetaOptional",
  1082  								Fields: []*FieldDiff{
  1083  									{
  1084  										Type: DiffTypeAdded,
  1085  										Name: "MetaOptional",
  1086  										Old:  "",
  1087  										New:  "bam",
  1088  									},
  1089  									{
  1090  										Type: DiffTypeDeleted,
  1091  										Name: "MetaOptional",
  1092  										Old:  "foo",
  1093  										New:  "",
  1094  									},
  1095  								},
  1096  							},
  1097  							{
  1098  								Type: DiffTypeEdited,
  1099  								Name: "MetaRequired",
  1100  								Fields: []*FieldDiff{
  1101  									{
  1102  										Type: DiffTypeAdded,
  1103  										Name: "MetaRequired",
  1104  										Old:  "",
  1105  										New:  "bang",
  1106  									},
  1107  									{
  1108  										Type: DiffTypeDeleted,
  1109  										Name: "MetaRequired",
  1110  										Old:  "bar",
  1111  										New:  "",
  1112  									},
  1113  								},
  1114  							},
  1115  						},
  1116  					},
  1117  				},
  1118  			},
  1119  		},
  1120  		{
  1121  			// Parameterized Job edited with context
  1122  			Contextual: true,
  1123  			Old: &Job{
  1124  				ParameterizedJob: &ParameterizedJobConfig{
  1125  					Payload:      DispatchPayloadRequired,
  1126  					MetaOptional: []string{"foo"},
  1127  					MetaRequired: []string{"bar"},
  1128  				},
  1129  			},
  1130  			New: &Job{
  1131  				ParameterizedJob: &ParameterizedJobConfig{
  1132  					Payload:      DispatchPayloadOptional,
  1133  					MetaOptional: []string{"foo"},
  1134  					MetaRequired: []string{"bar"},
  1135  				},
  1136  			},
  1137  			Expected: &JobDiff{
  1138  				Type: DiffTypeEdited,
  1139  				Objects: []*ObjectDiff{
  1140  					{
  1141  						Type: DiffTypeEdited,
  1142  						Name: "ParameterizedJob",
  1143  						Fields: []*FieldDiff{
  1144  							{
  1145  								Type: DiffTypeEdited,
  1146  								Name: "Payload",
  1147  								Old:  DispatchPayloadRequired,
  1148  								New:  DispatchPayloadOptional,
  1149  							},
  1150  						},
  1151  						Objects: []*ObjectDiff{
  1152  							{
  1153  								Type: DiffTypeNone,
  1154  								Name: "MetaOptional",
  1155  								Fields: []*FieldDiff{
  1156  									{
  1157  										Type: DiffTypeNone,
  1158  										Name: "MetaOptional",
  1159  										Old:  "foo",
  1160  										New:  "foo",
  1161  									},
  1162  								},
  1163  							},
  1164  							{
  1165  								Type: DiffTypeNone,
  1166  								Name: "MetaRequired",
  1167  								Fields: []*FieldDiff{
  1168  									{
  1169  										Type: DiffTypeNone,
  1170  										Name: "MetaRequired",
  1171  										Old:  "bar",
  1172  										New:  "bar",
  1173  									},
  1174  								},
  1175  							},
  1176  						},
  1177  					},
  1178  				},
  1179  			},
  1180  		},
  1181  	}
  1182  
  1183  	for i, c := range cases {
  1184  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1185  		if c.Error && err == nil {
  1186  			t.Fatalf("case %d: expected errored", i+1)
  1187  		} else if err != nil {
  1188  			if !c.Error {
  1189  				t.Fatalf("case %d: errored %#v", i+1, err)
  1190  			} else {
  1191  				continue
  1192  			}
  1193  		}
  1194  
  1195  		if !reflect.DeepEqual(actual, c.Expected) {
  1196  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1197  				i+1, actual, c.Expected)
  1198  		}
  1199  	}
  1200  }
  1201  
  1202  func TestTaskGroupDiff(t *testing.T) {
  1203  	cases := []struct {
  1204  		Old, New   *TaskGroup
  1205  		Expected   *TaskGroupDiff
  1206  		Error      bool
  1207  		Contextual bool
  1208  	}{
  1209  		{
  1210  			Old: nil,
  1211  			New: nil,
  1212  			Expected: &TaskGroupDiff{
  1213  				Type: DiffTypeNone,
  1214  			},
  1215  		},
  1216  		{
  1217  			// Primitive only that has different names
  1218  			Old: &TaskGroup{
  1219  				Name:  "foo",
  1220  				Count: 10,
  1221  				Meta: map[string]string{
  1222  					"foo": "bar",
  1223  				},
  1224  			},
  1225  			New: &TaskGroup{
  1226  				Name:  "bar",
  1227  				Count: 10,
  1228  				Meta: map[string]string{
  1229  					"foo": "bar",
  1230  				},
  1231  			},
  1232  			Error: true,
  1233  		},
  1234  		{
  1235  			// Primitive only that is the same
  1236  			Old: &TaskGroup{
  1237  				Name:  "foo",
  1238  				Count: 10,
  1239  				Meta: map[string]string{
  1240  					"foo": "bar",
  1241  				},
  1242  			},
  1243  			New: &TaskGroup{
  1244  				Name:  "foo",
  1245  				Count: 10,
  1246  				Meta: map[string]string{
  1247  					"foo": "bar",
  1248  				},
  1249  			},
  1250  			Expected: &TaskGroupDiff{
  1251  				Type: DiffTypeNone,
  1252  				Name: "foo",
  1253  			},
  1254  		},
  1255  		{
  1256  			// Primitive only that has diffs
  1257  			Old: &TaskGroup{
  1258  				Name:  "foo",
  1259  				Count: 10,
  1260  				Meta: map[string]string{
  1261  					"foo": "bar",
  1262  				},
  1263  			},
  1264  			New: &TaskGroup{
  1265  				Name:  "foo",
  1266  				Count: 100,
  1267  				Meta: map[string]string{
  1268  					"foo": "baz",
  1269  				},
  1270  			},
  1271  			Expected: &TaskGroupDiff{
  1272  				Type: DiffTypeEdited,
  1273  				Name: "foo",
  1274  				Fields: []*FieldDiff{
  1275  					{
  1276  						Type: DiffTypeEdited,
  1277  						Name: "Count",
  1278  						Old:  "10",
  1279  						New:  "100",
  1280  					},
  1281  					{
  1282  						Type: DiffTypeEdited,
  1283  						Name: "Meta[foo]",
  1284  						Old:  "bar",
  1285  						New:  "baz",
  1286  					},
  1287  				},
  1288  			},
  1289  		},
  1290  		{
  1291  			// Map diff
  1292  			Old: &TaskGroup{
  1293  				Meta: map[string]string{
  1294  					"foo": "foo",
  1295  					"bar": "bar",
  1296  				},
  1297  			},
  1298  			New: &TaskGroup{
  1299  				Meta: map[string]string{
  1300  					"bar": "bar",
  1301  					"baz": "baz",
  1302  				},
  1303  			},
  1304  			Expected: &TaskGroupDiff{
  1305  				Type: DiffTypeEdited,
  1306  				Fields: []*FieldDiff{
  1307  					{
  1308  						Type: DiffTypeAdded,
  1309  						Name: "Meta[baz]",
  1310  						Old:  "",
  1311  						New:  "baz",
  1312  					},
  1313  					{
  1314  						Type: DiffTypeDeleted,
  1315  						Name: "Meta[foo]",
  1316  						Old:  "foo",
  1317  						New:  "",
  1318  					},
  1319  				},
  1320  			},
  1321  		},
  1322  		{
  1323  			// Constraints edited
  1324  			Old: &TaskGroup{
  1325  				Constraints: []*Constraint{
  1326  					{
  1327  						LTarget: "foo",
  1328  						RTarget: "foo",
  1329  						Operand: "foo",
  1330  						str:     "foo",
  1331  					},
  1332  					{
  1333  						LTarget: "bar",
  1334  						RTarget: "bar",
  1335  						Operand: "bar",
  1336  						str:     "bar",
  1337  					},
  1338  				},
  1339  			},
  1340  			New: &TaskGroup{
  1341  				Constraints: []*Constraint{
  1342  					{
  1343  						LTarget: "foo",
  1344  						RTarget: "foo",
  1345  						Operand: "foo",
  1346  						str:     "foo",
  1347  					},
  1348  					{
  1349  						LTarget: "baz",
  1350  						RTarget: "baz",
  1351  						Operand: "baz",
  1352  						str:     "baz",
  1353  					},
  1354  				},
  1355  			},
  1356  			Expected: &TaskGroupDiff{
  1357  				Type: DiffTypeEdited,
  1358  				Objects: []*ObjectDiff{
  1359  					{
  1360  						Type: DiffTypeAdded,
  1361  						Name: "Constraint",
  1362  						Fields: []*FieldDiff{
  1363  							{
  1364  								Type: DiffTypeAdded,
  1365  								Name: "LTarget",
  1366  								Old:  "",
  1367  								New:  "baz",
  1368  							},
  1369  							{
  1370  								Type: DiffTypeAdded,
  1371  								Name: "Operand",
  1372  								Old:  "",
  1373  								New:  "baz",
  1374  							},
  1375  							{
  1376  								Type: DiffTypeAdded,
  1377  								Name: "RTarget",
  1378  								Old:  "",
  1379  								New:  "baz",
  1380  							},
  1381  						},
  1382  					},
  1383  					{
  1384  						Type: DiffTypeDeleted,
  1385  						Name: "Constraint",
  1386  						Fields: []*FieldDiff{
  1387  							{
  1388  								Type: DiffTypeDeleted,
  1389  								Name: "LTarget",
  1390  								Old:  "bar",
  1391  								New:  "",
  1392  							},
  1393  							{
  1394  								Type: DiffTypeDeleted,
  1395  								Name: "Operand",
  1396  								Old:  "bar",
  1397  								New:  "",
  1398  							},
  1399  							{
  1400  								Type: DiffTypeDeleted,
  1401  								Name: "RTarget",
  1402  								Old:  "bar",
  1403  								New:  "",
  1404  							},
  1405  						},
  1406  					},
  1407  				},
  1408  			},
  1409  		},
  1410  		{
  1411  			// Affinities edited
  1412  			Old: &TaskGroup{
  1413  				Affinities: []*Affinity{
  1414  					{
  1415  						LTarget: "foo",
  1416  						RTarget: "foo",
  1417  						Operand: "foo",
  1418  						Weight:  20,
  1419  						str:     "foo",
  1420  					},
  1421  					{
  1422  						LTarget: "bar",
  1423  						RTarget: "bar",
  1424  						Operand: "bar",
  1425  						Weight:  20,
  1426  						str:     "bar",
  1427  					},
  1428  				},
  1429  			},
  1430  			New: &TaskGroup{
  1431  				Affinities: []*Affinity{
  1432  					{
  1433  						LTarget: "foo",
  1434  						RTarget: "foo",
  1435  						Operand: "foo",
  1436  						Weight:  20,
  1437  						str:     "foo",
  1438  					},
  1439  					{
  1440  						LTarget: "baz",
  1441  						RTarget: "baz",
  1442  						Operand: "baz",
  1443  						Weight:  20,
  1444  						str:     "baz",
  1445  					},
  1446  				},
  1447  			},
  1448  			Expected: &TaskGroupDiff{
  1449  				Type: DiffTypeEdited,
  1450  				Objects: []*ObjectDiff{
  1451  					{
  1452  						Type: DiffTypeAdded,
  1453  						Name: "Affinity",
  1454  						Fields: []*FieldDiff{
  1455  							{
  1456  								Type: DiffTypeAdded,
  1457  								Name: "LTarget",
  1458  								Old:  "",
  1459  								New:  "baz",
  1460  							},
  1461  							{
  1462  								Type: DiffTypeAdded,
  1463  								Name: "Operand",
  1464  								Old:  "",
  1465  								New:  "baz",
  1466  							},
  1467  							{
  1468  								Type: DiffTypeAdded,
  1469  								Name: "RTarget",
  1470  								Old:  "",
  1471  								New:  "baz",
  1472  							},
  1473  							{
  1474  								Type: DiffTypeAdded,
  1475  								Name: "Weight",
  1476  								Old:  "",
  1477  								New:  "20",
  1478  							},
  1479  						},
  1480  					},
  1481  					{
  1482  						Type: DiffTypeDeleted,
  1483  						Name: "Affinity",
  1484  						Fields: []*FieldDiff{
  1485  							{
  1486  								Type: DiffTypeDeleted,
  1487  								Name: "LTarget",
  1488  								Old:  "bar",
  1489  								New:  "",
  1490  							},
  1491  							{
  1492  								Type: DiffTypeDeleted,
  1493  								Name: "Operand",
  1494  								Old:  "bar",
  1495  								New:  "",
  1496  							},
  1497  							{
  1498  								Type: DiffTypeDeleted,
  1499  								Name: "RTarget",
  1500  								Old:  "bar",
  1501  								New:  "",
  1502  							},
  1503  							{
  1504  								Type: DiffTypeDeleted,
  1505  								Name: "Weight",
  1506  								Old:  "20",
  1507  								New:  "",
  1508  							},
  1509  						},
  1510  					},
  1511  				},
  1512  			},
  1513  		},
  1514  		{
  1515  			// RestartPolicy added
  1516  			Old: &TaskGroup{},
  1517  			New: &TaskGroup{
  1518  				RestartPolicy: &RestartPolicy{
  1519  					Attempts: 1,
  1520  					Interval: 1 * time.Second,
  1521  					Delay:    1 * time.Second,
  1522  					Mode:     "fail",
  1523  				},
  1524  			},
  1525  			Expected: &TaskGroupDiff{
  1526  				Type: DiffTypeEdited,
  1527  				Objects: []*ObjectDiff{
  1528  					{
  1529  						Type: DiffTypeAdded,
  1530  						Name: "RestartPolicy",
  1531  						Fields: []*FieldDiff{
  1532  							{
  1533  								Type: DiffTypeAdded,
  1534  								Name: "Attempts",
  1535  								Old:  "",
  1536  								New:  "1",
  1537  							},
  1538  							{
  1539  								Type: DiffTypeAdded,
  1540  								Name: "Delay",
  1541  								Old:  "",
  1542  								New:  "1000000000",
  1543  							},
  1544  							{
  1545  								Type: DiffTypeAdded,
  1546  								Name: "Interval",
  1547  								Old:  "",
  1548  								New:  "1000000000",
  1549  							},
  1550  							{
  1551  								Type: DiffTypeAdded,
  1552  								Name: "Mode",
  1553  								Old:  "",
  1554  								New:  "fail",
  1555  							},
  1556  						},
  1557  					},
  1558  				},
  1559  			},
  1560  		},
  1561  		{
  1562  			// RestartPolicy deleted
  1563  			Old: &TaskGroup{
  1564  				RestartPolicy: &RestartPolicy{
  1565  					Attempts: 1,
  1566  					Interval: 1 * time.Second,
  1567  					Delay:    1 * time.Second,
  1568  					Mode:     "fail",
  1569  				},
  1570  			},
  1571  			New: &TaskGroup{},
  1572  			Expected: &TaskGroupDiff{
  1573  				Type: DiffTypeEdited,
  1574  				Objects: []*ObjectDiff{
  1575  					{
  1576  						Type: DiffTypeDeleted,
  1577  						Name: "RestartPolicy",
  1578  						Fields: []*FieldDiff{
  1579  							{
  1580  								Type: DiffTypeDeleted,
  1581  								Name: "Attempts",
  1582  								Old:  "1",
  1583  								New:  "",
  1584  							},
  1585  							{
  1586  								Type: DiffTypeDeleted,
  1587  								Name: "Delay",
  1588  								Old:  "1000000000",
  1589  								New:  "",
  1590  							},
  1591  							{
  1592  								Type: DiffTypeDeleted,
  1593  								Name: "Interval",
  1594  								Old:  "1000000000",
  1595  								New:  "",
  1596  							},
  1597  							{
  1598  								Type: DiffTypeDeleted,
  1599  								Name: "Mode",
  1600  								Old:  "fail",
  1601  								New:  "",
  1602  							},
  1603  						},
  1604  					},
  1605  				},
  1606  			},
  1607  		},
  1608  		{
  1609  			// RestartPolicy edited
  1610  			Old: &TaskGroup{
  1611  				RestartPolicy: &RestartPolicy{
  1612  					Attempts: 1,
  1613  					Interval: 1 * time.Second,
  1614  					Delay:    1 * time.Second,
  1615  					Mode:     "fail",
  1616  				},
  1617  			},
  1618  			New: &TaskGroup{
  1619  				RestartPolicy: &RestartPolicy{
  1620  					Attempts: 2,
  1621  					Interval: 2 * time.Second,
  1622  					Delay:    2 * time.Second,
  1623  					Mode:     "delay",
  1624  				},
  1625  			},
  1626  			Expected: &TaskGroupDiff{
  1627  				Type: DiffTypeEdited,
  1628  				Objects: []*ObjectDiff{
  1629  					{
  1630  						Type: DiffTypeEdited,
  1631  						Name: "RestartPolicy",
  1632  						Fields: []*FieldDiff{
  1633  							{
  1634  								Type: DiffTypeEdited,
  1635  								Name: "Attempts",
  1636  								Old:  "1",
  1637  								New:  "2",
  1638  							},
  1639  							{
  1640  								Type: DiffTypeEdited,
  1641  								Name: "Delay",
  1642  								Old:  "1000000000",
  1643  								New:  "2000000000",
  1644  							},
  1645  							{
  1646  								Type: DiffTypeEdited,
  1647  								Name: "Interval",
  1648  								Old:  "1000000000",
  1649  								New:  "2000000000",
  1650  							},
  1651  							{
  1652  								Type: DiffTypeEdited,
  1653  								Name: "Mode",
  1654  								Old:  "fail",
  1655  								New:  "delay",
  1656  							},
  1657  						},
  1658  					},
  1659  				},
  1660  			},
  1661  		},
  1662  		{
  1663  			// RestartPolicy edited with context
  1664  			Contextual: true,
  1665  			Old: &TaskGroup{
  1666  				RestartPolicy: &RestartPolicy{
  1667  					Attempts: 1,
  1668  					Interval: 1 * time.Second,
  1669  					Delay:    1 * time.Second,
  1670  					Mode:     "fail",
  1671  				},
  1672  			},
  1673  			New: &TaskGroup{
  1674  				RestartPolicy: &RestartPolicy{
  1675  					Attempts: 2,
  1676  					Interval: 2 * time.Second,
  1677  					Delay:    1 * time.Second,
  1678  					Mode:     "fail",
  1679  				},
  1680  			},
  1681  			Expected: &TaskGroupDiff{
  1682  				Type: DiffTypeEdited,
  1683  				Objects: []*ObjectDiff{
  1684  					{
  1685  						Type: DiffTypeEdited,
  1686  						Name: "RestartPolicy",
  1687  						Fields: []*FieldDiff{
  1688  							{
  1689  								Type: DiffTypeEdited,
  1690  								Name: "Attempts",
  1691  								Old:  "1",
  1692  								New:  "2",
  1693  							},
  1694  							{
  1695  								Type: DiffTypeNone,
  1696  								Name: "Delay",
  1697  								Old:  "1000000000",
  1698  								New:  "1000000000",
  1699  							},
  1700  							{
  1701  								Type: DiffTypeEdited,
  1702  								Name: "Interval",
  1703  								Old:  "1000000000",
  1704  								New:  "2000000000",
  1705  							},
  1706  							{
  1707  								Type: DiffTypeNone,
  1708  								Name: "Mode",
  1709  								Old:  "fail",
  1710  								New:  "fail",
  1711  							},
  1712  						},
  1713  					},
  1714  				},
  1715  			},
  1716  		},
  1717  		{
  1718  			// ReschedulePolicy added
  1719  			Old: &TaskGroup{},
  1720  			New: &TaskGroup{
  1721  				ReschedulePolicy: &ReschedulePolicy{
  1722  					Attempts:      1,
  1723  					Interval:      15 * time.Second,
  1724  					Delay:         5 * time.Second,
  1725  					MaxDelay:      20 * time.Second,
  1726  					DelayFunction: "exponential",
  1727  					Unlimited:     false,
  1728  				},
  1729  			},
  1730  			Expected: &TaskGroupDiff{
  1731  				Type: DiffTypeEdited,
  1732  				Objects: []*ObjectDiff{
  1733  					{
  1734  						Type: DiffTypeAdded,
  1735  						Name: "ReschedulePolicy",
  1736  						Fields: []*FieldDiff{
  1737  							{
  1738  								Type: DiffTypeAdded,
  1739  								Name: "Attempts",
  1740  								Old:  "",
  1741  								New:  "1",
  1742  							},
  1743  							{
  1744  								Type: DiffTypeAdded,
  1745  								Name: "Delay",
  1746  								Old:  "",
  1747  								New:  "5000000000",
  1748  							},
  1749  							{
  1750  								Type: DiffTypeAdded,
  1751  								Name: "DelayFunction",
  1752  								Old:  "",
  1753  								New:  "exponential",
  1754  							},
  1755  							{
  1756  								Type: DiffTypeAdded,
  1757  								Name: "Interval",
  1758  								Old:  "",
  1759  								New:  "15000000000",
  1760  							},
  1761  							{
  1762  								Type: DiffTypeAdded,
  1763  								Name: "MaxDelay",
  1764  								Old:  "",
  1765  								New:  "20000000000",
  1766  							},
  1767  							{
  1768  								Type: DiffTypeAdded,
  1769  								Name: "Unlimited",
  1770  								Old:  "",
  1771  								New:  "false",
  1772  							},
  1773  						},
  1774  					},
  1775  				},
  1776  			},
  1777  		},
  1778  		{
  1779  			// ReschedulePolicy deleted
  1780  			Old: &TaskGroup{
  1781  				ReschedulePolicy: &ReschedulePolicy{
  1782  					Attempts:      1,
  1783  					Interval:      15 * time.Second,
  1784  					Delay:         5 * time.Second,
  1785  					MaxDelay:      20 * time.Second,
  1786  					DelayFunction: "exponential",
  1787  					Unlimited:     false,
  1788  				},
  1789  			},
  1790  			New: &TaskGroup{},
  1791  			Expected: &TaskGroupDiff{
  1792  				Type: DiffTypeEdited,
  1793  				Objects: []*ObjectDiff{
  1794  					{
  1795  						Type: DiffTypeDeleted,
  1796  						Name: "ReschedulePolicy",
  1797  						Fields: []*FieldDiff{
  1798  							{
  1799  								Type: DiffTypeDeleted,
  1800  								Name: "Attempts",
  1801  								Old:  "1",
  1802  								New:  "",
  1803  							},
  1804  							{
  1805  								Type: DiffTypeDeleted,
  1806  								Name: "Delay",
  1807  								Old:  "5000000000",
  1808  								New:  "",
  1809  							},
  1810  							{
  1811  								Type: DiffTypeDeleted,
  1812  								Name: "DelayFunction",
  1813  								Old:  "exponential",
  1814  								New:  "",
  1815  							},
  1816  							{
  1817  								Type: DiffTypeDeleted,
  1818  								Name: "Interval",
  1819  								Old:  "15000000000",
  1820  								New:  "",
  1821  							},
  1822  							{
  1823  								Type: DiffTypeDeleted,
  1824  								Name: "MaxDelay",
  1825  								Old:  "20000000000",
  1826  								New:  "",
  1827  							},
  1828  							{
  1829  								Type: DiffTypeDeleted,
  1830  								Name: "Unlimited",
  1831  								Old:  "false",
  1832  								New:  "",
  1833  							},
  1834  						},
  1835  					},
  1836  				},
  1837  			},
  1838  		},
  1839  		{
  1840  			// ReschedulePolicy edited
  1841  			Old: &TaskGroup{
  1842  				ReschedulePolicy: &ReschedulePolicy{
  1843  					Attempts:      1,
  1844  					Interval:      1 * time.Second,
  1845  					DelayFunction: "exponential",
  1846  					Delay:         20 * time.Second,
  1847  					MaxDelay:      1 * time.Minute,
  1848  					Unlimited:     false,
  1849  				},
  1850  			},
  1851  			New: &TaskGroup{
  1852  				ReschedulePolicy: &ReschedulePolicy{
  1853  					Attempts:      2,
  1854  					Interval:      2 * time.Second,
  1855  					DelayFunction: "constant",
  1856  					Delay:         30 * time.Second,
  1857  					MaxDelay:      1 * time.Minute,
  1858  					Unlimited:     true,
  1859  				},
  1860  			},
  1861  			Expected: &TaskGroupDiff{
  1862  				Type: DiffTypeEdited,
  1863  				Objects: []*ObjectDiff{
  1864  					{
  1865  						Type: DiffTypeEdited,
  1866  						Name: "ReschedulePolicy",
  1867  						Fields: []*FieldDiff{
  1868  							{
  1869  								Type: DiffTypeEdited,
  1870  								Name: "Attempts",
  1871  								Old:  "1",
  1872  								New:  "2",
  1873  							},
  1874  							{
  1875  								Type: DiffTypeEdited,
  1876  								Name: "Delay",
  1877  								Old:  "20000000000",
  1878  								New:  "30000000000",
  1879  							},
  1880  							{
  1881  								Type: DiffTypeEdited,
  1882  								Name: "DelayFunction",
  1883  								Old:  "exponential",
  1884  								New:  "constant",
  1885  							},
  1886  							{
  1887  								Type: DiffTypeEdited,
  1888  								Name: "Interval",
  1889  								Old:  "1000000000",
  1890  								New:  "2000000000",
  1891  							},
  1892  							{
  1893  								Type: DiffTypeEdited,
  1894  								Name: "Unlimited",
  1895  								Old:  "false",
  1896  								New:  "true",
  1897  							},
  1898  						},
  1899  					},
  1900  				},
  1901  			},
  1902  		}, {
  1903  			// ReschedulePolicy edited with context
  1904  			Contextual: true,
  1905  			Old: &TaskGroup{
  1906  				ReschedulePolicy: &ReschedulePolicy{
  1907  					Attempts: 1,
  1908  					Interval: 1 * time.Second,
  1909  				},
  1910  			},
  1911  			New: &TaskGroup{
  1912  				ReschedulePolicy: &ReschedulePolicy{
  1913  					Attempts: 1,
  1914  					Interval: 2 * time.Second,
  1915  				},
  1916  			},
  1917  			Expected: &TaskGroupDiff{
  1918  				Type: DiffTypeEdited,
  1919  				Objects: []*ObjectDiff{
  1920  					{
  1921  						Type: DiffTypeEdited,
  1922  						Name: "ReschedulePolicy",
  1923  						Fields: []*FieldDiff{
  1924  							{
  1925  								Type: DiffTypeNone,
  1926  								Name: "Attempts",
  1927  								Old:  "1",
  1928  								New:  "1",
  1929  							},
  1930  							{
  1931  								Type: DiffTypeNone,
  1932  								Name: "Delay",
  1933  								Old:  "0",
  1934  								New:  "0",
  1935  							},
  1936  							{
  1937  								Type: DiffTypeNone,
  1938  								Name: "DelayFunction",
  1939  								Old:  "",
  1940  								New:  "",
  1941  							},
  1942  							{
  1943  								Type: DiffTypeEdited,
  1944  								Name: "Interval",
  1945  								Old:  "1000000000",
  1946  								New:  "2000000000",
  1947  							},
  1948  							{
  1949  								Type: DiffTypeNone,
  1950  								Name: "MaxDelay",
  1951  								Old:  "0",
  1952  								New:  "0",
  1953  							},
  1954  							{
  1955  								Type: DiffTypeNone,
  1956  								Name: "Unlimited",
  1957  								Old:  "false",
  1958  								New:  "false",
  1959  							},
  1960  						},
  1961  					},
  1962  				},
  1963  			},
  1964  		},
  1965  		{
  1966  			// Update strategy deleted
  1967  			Old: &TaskGroup{
  1968  				Update: &UpdateStrategy{
  1969  					AutoRevert: true,
  1970  				},
  1971  			},
  1972  			New: &TaskGroup{},
  1973  			Expected: &TaskGroupDiff{
  1974  				Type: DiffTypeEdited,
  1975  				Objects: []*ObjectDiff{
  1976  					{
  1977  						Type: DiffTypeDeleted,
  1978  						Name: "Update",
  1979  						Fields: []*FieldDiff{
  1980  							{
  1981  								Type: DiffTypeDeleted,
  1982  								Name: "AutoPromote",
  1983  								Old:  "false",
  1984  								New:  "",
  1985  							},
  1986  							{
  1987  								Type: DiffTypeDeleted,
  1988  								Name: "AutoRevert",
  1989  								Old:  "true",
  1990  								New:  "",
  1991  							},
  1992  							{
  1993  								Type: DiffTypeDeleted,
  1994  								Name: "Canary",
  1995  								Old:  "0",
  1996  								New:  "",
  1997  							},
  1998  							{
  1999  								Type: DiffTypeDeleted,
  2000  								Name: "HealthyDeadline",
  2001  								Old:  "0",
  2002  								New:  "",
  2003  							},
  2004  							{
  2005  								Type: DiffTypeDeleted,
  2006  								Name: "MaxParallel",
  2007  								Old:  "0",
  2008  								New:  "",
  2009  							},
  2010  							{
  2011  								Type: DiffTypeDeleted,
  2012  								Name: "MinHealthyTime",
  2013  								Old:  "0",
  2014  								New:  "",
  2015  							},
  2016  							{
  2017  								Type: DiffTypeDeleted,
  2018  								Name: "ProgressDeadline",
  2019  								Old:  "0",
  2020  								New:  "",
  2021  							},
  2022  						},
  2023  					},
  2024  				},
  2025  			},
  2026  		},
  2027  		{
  2028  			// Update strategy added
  2029  			Old: &TaskGroup{},
  2030  			New: &TaskGroup{
  2031  				Update: &UpdateStrategy{
  2032  					AutoRevert: true,
  2033  				},
  2034  			},
  2035  			Expected: &TaskGroupDiff{
  2036  				Type: DiffTypeEdited,
  2037  				Objects: []*ObjectDiff{
  2038  					{
  2039  						Type: DiffTypeAdded,
  2040  						Name: "Update",
  2041  						Fields: []*FieldDiff{
  2042  							{
  2043  								Type: DiffTypeAdded,
  2044  								Name: "AutoPromote",
  2045  								Old:  "",
  2046  								New:  "false",
  2047  							},
  2048  							{
  2049  								Type: DiffTypeAdded,
  2050  								Name: "AutoRevert",
  2051  								Old:  "",
  2052  								New:  "true",
  2053  							},
  2054  							{
  2055  								Type: DiffTypeAdded,
  2056  								Name: "Canary",
  2057  								Old:  "",
  2058  								New:  "0",
  2059  							},
  2060  							{
  2061  								Type: DiffTypeAdded,
  2062  								Name: "HealthyDeadline",
  2063  								Old:  "",
  2064  								New:  "0",
  2065  							},
  2066  							{
  2067  								Type: DiffTypeAdded,
  2068  								Name: "MaxParallel",
  2069  								Old:  "",
  2070  								New:  "0",
  2071  							},
  2072  							{
  2073  								Type: DiffTypeAdded,
  2074  								Name: "MinHealthyTime",
  2075  								Old:  "",
  2076  								New:  "0",
  2077  							},
  2078  							{
  2079  								Type: DiffTypeAdded,
  2080  								Name: "ProgressDeadline",
  2081  								Old:  "",
  2082  								New:  "0",
  2083  							},
  2084  						},
  2085  					},
  2086  				},
  2087  			},
  2088  		},
  2089  		{
  2090  			// Update strategy edited
  2091  			Old: &TaskGroup{
  2092  				Update: &UpdateStrategy{
  2093  					MaxParallel:      5,
  2094  					HealthCheck:      "foo",
  2095  					MinHealthyTime:   1 * time.Second,
  2096  					HealthyDeadline:  30 * time.Second,
  2097  					ProgressDeadline: 29 * time.Second,
  2098  					AutoRevert:       true,
  2099  					AutoPromote:      true,
  2100  					Canary:           2,
  2101  				},
  2102  			},
  2103  			New: &TaskGroup{
  2104  				Update: &UpdateStrategy{
  2105  					MaxParallel:      7,
  2106  					HealthCheck:      "bar",
  2107  					MinHealthyTime:   2 * time.Second,
  2108  					HealthyDeadline:  31 * time.Second,
  2109  					ProgressDeadline: 32 * time.Second,
  2110  					AutoRevert:       false,
  2111  					AutoPromote:      false,
  2112  					Canary:           1,
  2113  				},
  2114  			},
  2115  			Expected: &TaskGroupDiff{
  2116  				Type: DiffTypeEdited,
  2117  				Objects: []*ObjectDiff{
  2118  					{
  2119  						Type: DiffTypeEdited,
  2120  						Name: "Update",
  2121  						Fields: []*FieldDiff{
  2122  							{
  2123  								Type: DiffTypeEdited,
  2124  								Name: "AutoPromote",
  2125  								Old:  "true",
  2126  								New:  "false",
  2127  							},
  2128  							{
  2129  								Type: DiffTypeEdited,
  2130  								Name: "AutoRevert",
  2131  								Old:  "true",
  2132  								New:  "false",
  2133  							},
  2134  							{
  2135  								Type: DiffTypeEdited,
  2136  								Name: "Canary",
  2137  								Old:  "2",
  2138  								New:  "1",
  2139  							},
  2140  							{
  2141  								Type: DiffTypeEdited,
  2142  								Name: "HealthCheck",
  2143  								Old:  "foo",
  2144  								New:  "bar",
  2145  							},
  2146  							{
  2147  								Type: DiffTypeEdited,
  2148  								Name: "HealthyDeadline",
  2149  								Old:  "30000000000",
  2150  								New:  "31000000000",
  2151  							},
  2152  							{
  2153  								Type: DiffTypeEdited,
  2154  								Name: "MaxParallel",
  2155  								Old:  "5",
  2156  								New:  "7",
  2157  							},
  2158  							{
  2159  								Type: DiffTypeEdited,
  2160  								Name: "MinHealthyTime",
  2161  								Old:  "1000000000",
  2162  								New:  "2000000000",
  2163  							},
  2164  							{
  2165  								Type: DiffTypeEdited,
  2166  								Name: "ProgressDeadline",
  2167  								Old:  "29000000000",
  2168  								New:  "32000000000",
  2169  							},
  2170  						},
  2171  					},
  2172  				},
  2173  			},
  2174  		},
  2175  		{
  2176  			// Update strategy edited with context
  2177  			Contextual: true,
  2178  			Old: &TaskGroup{
  2179  				Update: &UpdateStrategy{
  2180  					MaxParallel:      5,
  2181  					HealthCheck:      "foo",
  2182  					MinHealthyTime:   1 * time.Second,
  2183  					HealthyDeadline:  30 * time.Second,
  2184  					ProgressDeadline: 30 * time.Second,
  2185  					AutoRevert:       true,
  2186  					AutoPromote:      true,
  2187  					Canary:           2,
  2188  				},
  2189  			},
  2190  			New: &TaskGroup{
  2191  				Update: &UpdateStrategy{
  2192  					MaxParallel:      7,
  2193  					HealthCheck:      "foo",
  2194  					MinHealthyTime:   1 * time.Second,
  2195  					HealthyDeadline:  30 * time.Second,
  2196  					ProgressDeadline: 30 * time.Second,
  2197  					AutoRevert:       true,
  2198  					AutoPromote:      true,
  2199  					Canary:           2,
  2200  				},
  2201  			},
  2202  			Expected: &TaskGroupDiff{
  2203  				Type: DiffTypeEdited,
  2204  				Objects: []*ObjectDiff{
  2205  					{
  2206  						Type: DiffTypeEdited,
  2207  						Name: "Update",
  2208  						Fields: []*FieldDiff{
  2209  							{
  2210  								Type: DiffTypeNone,
  2211  								Name: "AutoPromote",
  2212  								Old:  "true",
  2213  								New:  "true",
  2214  							},
  2215  							{
  2216  								Type: DiffTypeNone,
  2217  								Name: "AutoRevert",
  2218  								Old:  "true",
  2219  								New:  "true",
  2220  							},
  2221  							{
  2222  								Type: DiffTypeNone,
  2223  								Name: "Canary",
  2224  								Old:  "2",
  2225  								New:  "2",
  2226  							},
  2227  							{
  2228  								Type: DiffTypeNone,
  2229  								Name: "HealthCheck",
  2230  								Old:  "foo",
  2231  								New:  "foo",
  2232  							},
  2233  							{
  2234  								Type: DiffTypeNone,
  2235  								Name: "HealthyDeadline",
  2236  								Old:  "30000000000",
  2237  								New:  "30000000000",
  2238  							},
  2239  							{
  2240  								Type: DiffTypeEdited,
  2241  								Name: "MaxParallel",
  2242  								Old:  "5",
  2243  								New:  "7",
  2244  							},
  2245  							{
  2246  								Type: DiffTypeNone,
  2247  								Name: "MinHealthyTime",
  2248  								Old:  "1000000000",
  2249  								New:  "1000000000",
  2250  							},
  2251  							{
  2252  								Type: DiffTypeNone,
  2253  								Name: "ProgressDeadline",
  2254  								Old:  "30000000000",
  2255  								New:  "30000000000",
  2256  							},
  2257  						},
  2258  					},
  2259  				},
  2260  			},
  2261  		},
  2262  		{
  2263  			// EphemeralDisk added
  2264  			Old: &TaskGroup{},
  2265  			New: &TaskGroup{
  2266  				EphemeralDisk: &EphemeralDisk{
  2267  					Migrate: true,
  2268  					Sticky:  true,
  2269  					SizeMB:  100,
  2270  				},
  2271  			},
  2272  			Expected: &TaskGroupDiff{
  2273  				Type: DiffTypeEdited,
  2274  				Objects: []*ObjectDiff{
  2275  					{
  2276  						Type: DiffTypeAdded,
  2277  						Name: "EphemeralDisk",
  2278  						Fields: []*FieldDiff{
  2279  							{
  2280  								Type: DiffTypeAdded,
  2281  								Name: "Migrate",
  2282  								Old:  "",
  2283  								New:  "true",
  2284  							},
  2285  							{
  2286  								Type: DiffTypeAdded,
  2287  								Name: "SizeMB",
  2288  								Old:  "",
  2289  								New:  "100",
  2290  							},
  2291  							{
  2292  								Type: DiffTypeAdded,
  2293  								Name: "Sticky",
  2294  								Old:  "",
  2295  								New:  "true",
  2296  							},
  2297  						},
  2298  					},
  2299  				},
  2300  			},
  2301  		},
  2302  		{
  2303  			// EphemeralDisk deleted
  2304  			Old: &TaskGroup{
  2305  				EphemeralDisk: &EphemeralDisk{
  2306  					Migrate: true,
  2307  					Sticky:  true,
  2308  					SizeMB:  100,
  2309  				},
  2310  			},
  2311  			New: &TaskGroup{},
  2312  			Expected: &TaskGroupDiff{
  2313  				Type: DiffTypeEdited,
  2314  				Objects: []*ObjectDiff{
  2315  					{
  2316  						Type: DiffTypeDeleted,
  2317  						Name: "EphemeralDisk",
  2318  						Fields: []*FieldDiff{
  2319  							{
  2320  								Type: DiffTypeDeleted,
  2321  								Name: "Migrate",
  2322  								Old:  "true",
  2323  								New:  "",
  2324  							},
  2325  							{
  2326  								Type: DiffTypeDeleted,
  2327  								Name: "SizeMB",
  2328  								Old:  "100",
  2329  								New:  "",
  2330  							},
  2331  							{
  2332  								Type: DiffTypeDeleted,
  2333  								Name: "Sticky",
  2334  								Old:  "true",
  2335  								New:  "",
  2336  							},
  2337  						},
  2338  					},
  2339  				},
  2340  			},
  2341  		},
  2342  		{
  2343  			// EphemeralDisk edited
  2344  			Old: &TaskGroup{
  2345  				EphemeralDisk: &EphemeralDisk{
  2346  					Migrate: true,
  2347  					Sticky:  true,
  2348  					SizeMB:  150,
  2349  				},
  2350  			},
  2351  			New: &TaskGroup{
  2352  				EphemeralDisk: &EphemeralDisk{
  2353  					Migrate: false,
  2354  					Sticky:  false,
  2355  					SizeMB:  90,
  2356  				},
  2357  			},
  2358  			Expected: &TaskGroupDiff{
  2359  				Type: DiffTypeEdited,
  2360  				Objects: []*ObjectDiff{
  2361  					{
  2362  						Type: DiffTypeEdited,
  2363  						Name: "EphemeralDisk",
  2364  						Fields: []*FieldDiff{
  2365  							{
  2366  								Type: DiffTypeEdited,
  2367  								Name: "Migrate",
  2368  								Old:  "true",
  2369  								New:  "false",
  2370  							},
  2371  							{
  2372  								Type: DiffTypeEdited,
  2373  								Name: "SizeMB",
  2374  								Old:  "150",
  2375  								New:  "90",
  2376  							},
  2377  
  2378  							{
  2379  								Type: DiffTypeEdited,
  2380  								Name: "Sticky",
  2381  								Old:  "true",
  2382  								New:  "false",
  2383  							},
  2384  						},
  2385  					},
  2386  				},
  2387  			},
  2388  		},
  2389  		{
  2390  			// EphemeralDisk edited with context
  2391  			Contextual: true,
  2392  			Old: &TaskGroup{
  2393  				EphemeralDisk: &EphemeralDisk{
  2394  					Migrate: false,
  2395  					Sticky:  false,
  2396  					SizeMB:  100,
  2397  				},
  2398  			},
  2399  			New: &TaskGroup{
  2400  				EphemeralDisk: &EphemeralDisk{
  2401  					Migrate: true,
  2402  					Sticky:  true,
  2403  					SizeMB:  90,
  2404  				},
  2405  			},
  2406  			Expected: &TaskGroupDiff{
  2407  				Type: DiffTypeEdited,
  2408  				Objects: []*ObjectDiff{
  2409  					{
  2410  						Type: DiffTypeEdited,
  2411  						Name: "EphemeralDisk",
  2412  						Fields: []*FieldDiff{
  2413  							{
  2414  								Type: DiffTypeEdited,
  2415  								Name: "Migrate",
  2416  								Old:  "false",
  2417  								New:  "true",
  2418  							},
  2419  							{
  2420  								Type: DiffTypeEdited,
  2421  								Name: "SizeMB",
  2422  								Old:  "100",
  2423  								New:  "90",
  2424  							},
  2425  							{
  2426  								Type: DiffTypeEdited,
  2427  								Name: "Sticky",
  2428  								Old:  "false",
  2429  								New:  "true",
  2430  							},
  2431  						},
  2432  					},
  2433  				},
  2434  			},
  2435  		},
  2436  		{
  2437  			// Tasks edited
  2438  			Old: &TaskGroup{
  2439  				Tasks: []*Task{
  2440  					{
  2441  						Name:   "foo",
  2442  						Driver: "docker",
  2443  					},
  2444  					{
  2445  						Name:   "bar",
  2446  						Driver: "docker",
  2447  					},
  2448  					{
  2449  						Name:          "baz",
  2450  						ShutdownDelay: 1 * time.Second,
  2451  					},
  2452  				},
  2453  			},
  2454  			New: &TaskGroup{
  2455  				Tasks: []*Task{
  2456  					{
  2457  						Name:   "bar",
  2458  						Driver: "docker",
  2459  					},
  2460  					{
  2461  						Name:   "bam",
  2462  						Driver: "docker",
  2463  					},
  2464  					{
  2465  						Name:          "baz",
  2466  						ShutdownDelay: 2 * time.Second,
  2467  					},
  2468  				},
  2469  			},
  2470  			Expected: &TaskGroupDiff{
  2471  				Type: DiffTypeEdited,
  2472  				Tasks: []*TaskDiff{
  2473  					{
  2474  						Type: DiffTypeAdded,
  2475  						Name: "bam",
  2476  						Fields: []*FieldDiff{
  2477  							{
  2478  								Type: DiffTypeAdded,
  2479  								Name: "Driver",
  2480  								Old:  "",
  2481  								New:  "docker",
  2482  							},
  2483  							{
  2484  								Type: DiffTypeAdded,
  2485  								Name: "KillTimeout",
  2486  								Old:  "",
  2487  								New:  "0",
  2488  							},
  2489  							{
  2490  								Type: DiffTypeAdded,
  2491  								Name: "Leader",
  2492  								Old:  "",
  2493  								New:  "false",
  2494  							},
  2495  							{
  2496  								Type: DiffTypeAdded,
  2497  								Name: "ShutdownDelay",
  2498  								Old:  "",
  2499  								New:  "0",
  2500  							},
  2501  						},
  2502  					},
  2503  					{
  2504  						Type: DiffTypeNone,
  2505  						Name: "bar",
  2506  					},
  2507  					{
  2508  						Type: DiffTypeEdited,
  2509  						Name: "baz",
  2510  						Fields: []*FieldDiff{
  2511  							{
  2512  								Type: DiffTypeEdited,
  2513  								Name: "ShutdownDelay",
  2514  								Old:  "1000000000",
  2515  								New:  "2000000000",
  2516  							},
  2517  						},
  2518  					},
  2519  					{
  2520  						Type: DiffTypeDeleted,
  2521  						Name: "foo",
  2522  						Fields: []*FieldDiff{
  2523  							{
  2524  								Type: DiffTypeDeleted,
  2525  								Name: "Driver",
  2526  								Old:  "docker",
  2527  								New:  "",
  2528  							},
  2529  							{
  2530  								Type: DiffTypeDeleted,
  2531  								Name: "KillTimeout",
  2532  								Old:  "0",
  2533  								New:  "",
  2534  							},
  2535  							{
  2536  								Type: DiffTypeDeleted,
  2537  								Name: "Leader",
  2538  								Old:  "false",
  2539  								New:  "",
  2540  							},
  2541  							{
  2542  								Type: DiffTypeDeleted,
  2543  								Name: "ShutdownDelay",
  2544  								Old:  "0",
  2545  								New:  "",
  2546  							},
  2547  						},
  2548  					},
  2549  				},
  2550  			},
  2551  		},
  2552  	}
  2553  
  2554  	for i, c := range cases {
  2555  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2556  		if c.Error && err == nil {
  2557  			t.Fatalf("case %d: expected errored", i+1)
  2558  		} else if err != nil {
  2559  			if !c.Error {
  2560  				t.Fatalf("case %d: errored %#v", i+1, err)
  2561  			} else {
  2562  				continue
  2563  			}
  2564  		}
  2565  
  2566  		if !reflect.DeepEqual(actual, c.Expected) {
  2567  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2568  				i+1, actual, c.Expected)
  2569  		}
  2570  	}
  2571  }
  2572  
  2573  func TestTaskDiff(t *testing.T) {
  2574  	cases := []struct {
  2575  		Name       string
  2576  		Old, New   *Task
  2577  		Expected   *TaskDiff
  2578  		Error      bool
  2579  		Contextual bool
  2580  	}{
  2581  		{
  2582  			Name: "Empty",
  2583  			Old:  nil,
  2584  			New:  nil,
  2585  			Expected: &TaskDiff{
  2586  				Type: DiffTypeNone,
  2587  			},
  2588  		},
  2589  		{
  2590  			Name: "Primitive only that has different names",
  2591  			Old: &Task{
  2592  				Name: "foo",
  2593  				Meta: map[string]string{
  2594  					"foo": "bar",
  2595  				},
  2596  			},
  2597  			New: &Task{
  2598  				Name: "bar",
  2599  				Meta: map[string]string{
  2600  					"foo": "bar",
  2601  				},
  2602  			},
  2603  			Error: true,
  2604  		},
  2605  		{
  2606  			Name: "Primitive only that is the same",
  2607  			Old: &Task{
  2608  				Name:   "foo",
  2609  				Driver: "exec",
  2610  				User:   "foo",
  2611  				Env: map[string]string{
  2612  					"FOO": "bar",
  2613  				},
  2614  				Meta: map[string]string{
  2615  					"foo": "bar",
  2616  				},
  2617  				KillTimeout: 1 * time.Second,
  2618  				Leader:      true,
  2619  			},
  2620  			New: &Task{
  2621  				Name:   "foo",
  2622  				Driver: "exec",
  2623  				User:   "foo",
  2624  				Env: map[string]string{
  2625  					"FOO": "bar",
  2626  				},
  2627  				Meta: map[string]string{
  2628  					"foo": "bar",
  2629  				},
  2630  				KillTimeout: 1 * time.Second,
  2631  				Leader:      true,
  2632  			},
  2633  			Expected: &TaskDiff{
  2634  				Type: DiffTypeNone,
  2635  				Name: "foo",
  2636  			},
  2637  		},
  2638  		{
  2639  			Name: "Primitive only that has diffs",
  2640  			Old: &Task{
  2641  				Name:   "foo",
  2642  				Driver: "exec",
  2643  				User:   "foo",
  2644  				Env: map[string]string{
  2645  					"FOO": "bar",
  2646  				},
  2647  				Meta: map[string]string{
  2648  					"foo": "bar",
  2649  				},
  2650  				KillTimeout: 1 * time.Second,
  2651  				Leader:      true,
  2652  			},
  2653  			New: &Task{
  2654  				Name:   "foo",
  2655  				Driver: "docker",
  2656  				User:   "bar",
  2657  				Env: map[string]string{
  2658  					"FOO": "baz",
  2659  				},
  2660  				Meta: map[string]string{
  2661  					"foo": "baz",
  2662  				},
  2663  				KillTimeout: 2 * time.Second,
  2664  				Leader:      false,
  2665  			},
  2666  			Expected: &TaskDiff{
  2667  				Type: DiffTypeEdited,
  2668  				Name: "foo",
  2669  				Fields: []*FieldDiff{
  2670  					{
  2671  						Type: DiffTypeEdited,
  2672  						Name: "Driver",
  2673  						Old:  "exec",
  2674  						New:  "docker",
  2675  					},
  2676  					{
  2677  						Type: DiffTypeEdited,
  2678  						Name: "Env[FOO]",
  2679  						Old:  "bar",
  2680  						New:  "baz",
  2681  					},
  2682  					{
  2683  						Type: DiffTypeEdited,
  2684  						Name: "KillTimeout",
  2685  						Old:  "1000000000",
  2686  						New:  "2000000000",
  2687  					},
  2688  					{
  2689  						Type: DiffTypeEdited,
  2690  						Name: "Leader",
  2691  						Old:  "true",
  2692  						New:  "false",
  2693  					},
  2694  					{
  2695  						Type: DiffTypeEdited,
  2696  						Name: "Meta[foo]",
  2697  						Old:  "bar",
  2698  						New:  "baz",
  2699  					},
  2700  					{
  2701  						Type: DiffTypeEdited,
  2702  						Name: "User",
  2703  						Old:  "foo",
  2704  						New:  "bar",
  2705  					},
  2706  				},
  2707  			},
  2708  		},
  2709  		{
  2710  			Name: "Map diff",
  2711  			Old: &Task{
  2712  				Meta: map[string]string{
  2713  					"foo": "foo",
  2714  					"bar": "bar",
  2715  				},
  2716  				Env: map[string]string{
  2717  					"foo": "foo",
  2718  					"bar": "bar",
  2719  				},
  2720  			},
  2721  			New: &Task{
  2722  				Meta: map[string]string{
  2723  					"bar": "bar",
  2724  					"baz": "baz",
  2725  				},
  2726  				Env: map[string]string{
  2727  					"bar": "bar",
  2728  					"baz": "baz",
  2729  				},
  2730  			},
  2731  			Expected: &TaskDiff{
  2732  				Type: DiffTypeEdited,
  2733  				Fields: []*FieldDiff{
  2734  					{
  2735  						Type: DiffTypeAdded,
  2736  						Name: "Env[baz]",
  2737  						Old:  "",
  2738  						New:  "baz",
  2739  					},
  2740  					{
  2741  						Type: DiffTypeDeleted,
  2742  						Name: "Env[foo]",
  2743  						Old:  "foo",
  2744  						New:  "",
  2745  					},
  2746  					{
  2747  						Type: DiffTypeAdded,
  2748  						Name: "Meta[baz]",
  2749  						Old:  "",
  2750  						New:  "baz",
  2751  					},
  2752  					{
  2753  						Type: DiffTypeDeleted,
  2754  						Name: "Meta[foo]",
  2755  						Old:  "foo",
  2756  						New:  "",
  2757  					},
  2758  				},
  2759  			},
  2760  		},
  2761  		{
  2762  			Name: "Constraints edited",
  2763  			Old: &Task{
  2764  				Constraints: []*Constraint{
  2765  					{
  2766  						LTarget: "foo",
  2767  						RTarget: "foo",
  2768  						Operand: "foo",
  2769  						str:     "foo",
  2770  					},
  2771  					{
  2772  						LTarget: "bar",
  2773  						RTarget: "bar",
  2774  						Operand: "bar",
  2775  						str:     "bar",
  2776  					},
  2777  				},
  2778  			},
  2779  			New: &Task{
  2780  				Constraints: []*Constraint{
  2781  					{
  2782  						LTarget: "foo",
  2783  						RTarget: "foo",
  2784  						Operand: "foo",
  2785  						str:     "foo",
  2786  					},
  2787  					{
  2788  						LTarget: "baz",
  2789  						RTarget: "baz",
  2790  						Operand: "baz",
  2791  						str:     "baz",
  2792  					},
  2793  				},
  2794  			},
  2795  			Expected: &TaskDiff{
  2796  				Type: DiffTypeEdited,
  2797  				Objects: []*ObjectDiff{
  2798  					{
  2799  						Type: DiffTypeAdded,
  2800  						Name: "Constraint",
  2801  						Fields: []*FieldDiff{
  2802  							{
  2803  								Type: DiffTypeAdded,
  2804  								Name: "LTarget",
  2805  								Old:  "",
  2806  								New:  "baz",
  2807  							},
  2808  							{
  2809  								Type: DiffTypeAdded,
  2810  								Name: "Operand",
  2811  								Old:  "",
  2812  								New:  "baz",
  2813  							},
  2814  							{
  2815  								Type: DiffTypeAdded,
  2816  								Name: "RTarget",
  2817  								Old:  "",
  2818  								New:  "baz",
  2819  							},
  2820  						},
  2821  					},
  2822  					{
  2823  						Type: DiffTypeDeleted,
  2824  						Name: "Constraint",
  2825  						Fields: []*FieldDiff{
  2826  							{
  2827  								Type: DiffTypeDeleted,
  2828  								Name: "LTarget",
  2829  								Old:  "bar",
  2830  								New:  "",
  2831  							},
  2832  							{
  2833  								Type: DiffTypeDeleted,
  2834  								Name: "Operand",
  2835  								Old:  "bar",
  2836  								New:  "",
  2837  							},
  2838  							{
  2839  								Type: DiffTypeDeleted,
  2840  								Name: "RTarget",
  2841  								Old:  "bar",
  2842  								New:  "",
  2843  							},
  2844  						},
  2845  					},
  2846  				},
  2847  			},
  2848  		},
  2849  		{
  2850  			Name: "Affinities edited",
  2851  			Old: &Task{
  2852  				Affinities: []*Affinity{
  2853  					{
  2854  						LTarget: "foo",
  2855  						RTarget: "foo",
  2856  						Operand: "foo",
  2857  						Weight:  20,
  2858  						str:     "foo",
  2859  					},
  2860  					{
  2861  						LTarget: "bar",
  2862  						RTarget: "bar",
  2863  						Operand: "bar",
  2864  						Weight:  20,
  2865  						str:     "bar",
  2866  					},
  2867  				},
  2868  			},
  2869  			New: &Task{
  2870  				Affinities: []*Affinity{
  2871  					{
  2872  						LTarget: "foo",
  2873  						RTarget: "foo",
  2874  						Operand: "foo",
  2875  						Weight:  20,
  2876  						str:     "foo",
  2877  					},
  2878  					{
  2879  						LTarget: "baz",
  2880  						RTarget: "baz",
  2881  						Operand: "baz",
  2882  						Weight:  20,
  2883  						str:     "baz",
  2884  					},
  2885  				},
  2886  			},
  2887  			Expected: &TaskDiff{
  2888  				Type: DiffTypeEdited,
  2889  				Objects: []*ObjectDiff{
  2890  					{
  2891  						Type: DiffTypeAdded,
  2892  						Name: "Affinity",
  2893  						Fields: []*FieldDiff{
  2894  							{
  2895  								Type: DiffTypeAdded,
  2896  								Name: "LTarget",
  2897  								Old:  "",
  2898  								New:  "baz",
  2899  							},
  2900  							{
  2901  								Type: DiffTypeAdded,
  2902  								Name: "Operand",
  2903  								Old:  "",
  2904  								New:  "baz",
  2905  							},
  2906  							{
  2907  								Type: DiffTypeAdded,
  2908  								Name: "RTarget",
  2909  								Old:  "",
  2910  								New:  "baz",
  2911  							},
  2912  							{
  2913  								Type: DiffTypeAdded,
  2914  								Name: "Weight",
  2915  								Old:  "",
  2916  								New:  "20",
  2917  							},
  2918  						},
  2919  					},
  2920  					{
  2921  						Type: DiffTypeDeleted,
  2922  						Name: "Affinity",
  2923  						Fields: []*FieldDiff{
  2924  							{
  2925  								Type: DiffTypeDeleted,
  2926  								Name: "LTarget",
  2927  								Old:  "bar",
  2928  								New:  "",
  2929  							},
  2930  							{
  2931  								Type: DiffTypeDeleted,
  2932  								Name: "Operand",
  2933  								Old:  "bar",
  2934  								New:  "",
  2935  							},
  2936  							{
  2937  								Type: DiffTypeDeleted,
  2938  								Name: "RTarget",
  2939  								Old:  "bar",
  2940  								New:  "",
  2941  							},
  2942  							{
  2943  								Type: DiffTypeDeleted,
  2944  								Name: "Weight",
  2945  								Old:  "20",
  2946  								New:  "",
  2947  							},
  2948  						},
  2949  					},
  2950  				},
  2951  			},
  2952  		},
  2953  		{
  2954  			Name: "LogConfig added",
  2955  			Old:  &Task{},
  2956  			New: &Task{
  2957  				LogConfig: &LogConfig{
  2958  					MaxFiles:      1,
  2959  					MaxFileSizeMB: 10,
  2960  				},
  2961  			},
  2962  			Expected: &TaskDiff{
  2963  				Type: DiffTypeEdited,
  2964  				Objects: []*ObjectDiff{
  2965  					{
  2966  						Type: DiffTypeAdded,
  2967  						Name: "LogConfig",
  2968  						Fields: []*FieldDiff{
  2969  							{
  2970  								Type: DiffTypeAdded,
  2971  								Name: "MaxFileSizeMB",
  2972  								Old:  "",
  2973  								New:  "10",
  2974  							},
  2975  							{
  2976  								Type: DiffTypeAdded,
  2977  								Name: "MaxFiles",
  2978  								Old:  "",
  2979  								New:  "1",
  2980  							},
  2981  						},
  2982  					},
  2983  				},
  2984  			},
  2985  		},
  2986  		{
  2987  			Name: "LogConfig deleted",
  2988  			Old: &Task{
  2989  				LogConfig: &LogConfig{
  2990  					MaxFiles:      1,
  2991  					MaxFileSizeMB: 10,
  2992  				},
  2993  			},
  2994  			New: &Task{},
  2995  			Expected: &TaskDiff{
  2996  				Type: DiffTypeEdited,
  2997  				Objects: []*ObjectDiff{
  2998  					{
  2999  						Type: DiffTypeDeleted,
  3000  						Name: "LogConfig",
  3001  						Fields: []*FieldDiff{
  3002  							{
  3003  								Type: DiffTypeDeleted,
  3004  								Name: "MaxFileSizeMB",
  3005  								Old:  "10",
  3006  								New:  "",
  3007  							},
  3008  							{
  3009  								Type: DiffTypeDeleted,
  3010  								Name: "MaxFiles",
  3011  								Old:  "1",
  3012  								New:  "",
  3013  							},
  3014  						},
  3015  					},
  3016  				},
  3017  			},
  3018  		},
  3019  		{
  3020  			Name: "LogConfig edited",
  3021  			Old: &Task{
  3022  				LogConfig: &LogConfig{
  3023  					MaxFiles:      1,
  3024  					MaxFileSizeMB: 10,
  3025  				},
  3026  			},
  3027  			New: &Task{
  3028  				LogConfig: &LogConfig{
  3029  					MaxFiles:      2,
  3030  					MaxFileSizeMB: 20,
  3031  				},
  3032  			},
  3033  			Expected: &TaskDiff{
  3034  				Type: DiffTypeEdited,
  3035  				Objects: []*ObjectDiff{
  3036  					{
  3037  						Type: DiffTypeEdited,
  3038  						Name: "LogConfig",
  3039  						Fields: []*FieldDiff{
  3040  							{
  3041  								Type: DiffTypeEdited,
  3042  								Name: "MaxFileSizeMB",
  3043  								Old:  "10",
  3044  								New:  "20",
  3045  							},
  3046  							{
  3047  								Type: DiffTypeEdited,
  3048  								Name: "MaxFiles",
  3049  								Old:  "1",
  3050  								New:  "2",
  3051  							},
  3052  						},
  3053  					},
  3054  				},
  3055  			},
  3056  		},
  3057  		{
  3058  			Name:       "LogConfig edited with context",
  3059  			Contextual: true,
  3060  			Old: &Task{
  3061  				LogConfig: &LogConfig{
  3062  					MaxFiles:      1,
  3063  					MaxFileSizeMB: 10,
  3064  				},
  3065  			},
  3066  			New: &Task{
  3067  				LogConfig: &LogConfig{
  3068  					MaxFiles:      1,
  3069  					MaxFileSizeMB: 20,
  3070  				},
  3071  			},
  3072  			Expected: &TaskDiff{
  3073  				Type: DiffTypeEdited,
  3074  				Objects: []*ObjectDiff{
  3075  					{
  3076  						Type: DiffTypeEdited,
  3077  						Name: "LogConfig",
  3078  						Fields: []*FieldDiff{
  3079  							{
  3080  								Type: DiffTypeEdited,
  3081  								Name: "MaxFileSizeMB",
  3082  								Old:  "10",
  3083  								New:  "20",
  3084  							},
  3085  							{
  3086  								Type: DiffTypeNone,
  3087  								Name: "MaxFiles",
  3088  								Old:  "1",
  3089  								New:  "1",
  3090  							},
  3091  						},
  3092  					},
  3093  				},
  3094  			},
  3095  		},
  3096  		{
  3097  			Name: "Artifacts edited",
  3098  			Old: &Task{
  3099  				Artifacts: []*TaskArtifact{
  3100  					{
  3101  						GetterSource: "foo",
  3102  						GetterOptions: map[string]string{
  3103  							"foo": "bar",
  3104  						},
  3105  						RelativeDest: "foo",
  3106  					},
  3107  					{
  3108  						GetterSource: "bar",
  3109  						GetterOptions: map[string]string{
  3110  							"bar": "baz",
  3111  						},
  3112  						GetterMode:   "dir",
  3113  						RelativeDest: "bar",
  3114  					},
  3115  				},
  3116  			},
  3117  			New: &Task{
  3118  				Artifacts: []*TaskArtifact{
  3119  					{
  3120  						GetterSource: "foo",
  3121  						GetterOptions: map[string]string{
  3122  							"foo": "bar",
  3123  						},
  3124  						RelativeDest: "foo",
  3125  					},
  3126  					{
  3127  						GetterSource: "bam",
  3128  						GetterOptions: map[string]string{
  3129  							"bam": "baz",
  3130  						},
  3131  						GetterMode:   "file",
  3132  						RelativeDest: "bam",
  3133  					},
  3134  				},
  3135  			},
  3136  			Expected: &TaskDiff{
  3137  				Type: DiffTypeEdited,
  3138  				Objects: []*ObjectDiff{
  3139  					{
  3140  						Type: DiffTypeAdded,
  3141  						Name: "Artifact",
  3142  						Fields: []*FieldDiff{
  3143  							{
  3144  								Type: DiffTypeAdded,
  3145  								Name: "GetterMode",
  3146  								Old:  "",
  3147  								New:  "file",
  3148  							},
  3149  							{
  3150  								Type: DiffTypeAdded,
  3151  								Name: "GetterOptions[bam]",
  3152  								Old:  "",
  3153  								New:  "baz",
  3154  							},
  3155  							{
  3156  								Type: DiffTypeAdded,
  3157  								Name: "GetterSource",
  3158  								Old:  "",
  3159  								New:  "bam",
  3160  							},
  3161  							{
  3162  								Type: DiffTypeAdded,
  3163  								Name: "RelativeDest",
  3164  								Old:  "",
  3165  								New:  "bam",
  3166  							},
  3167  						},
  3168  					},
  3169  					{
  3170  						Type: DiffTypeDeleted,
  3171  						Name: "Artifact",
  3172  						Fields: []*FieldDiff{
  3173  							{
  3174  								Type: DiffTypeDeleted,
  3175  								Name: "GetterMode",
  3176  								Old:  "dir",
  3177  								New:  "",
  3178  							},
  3179  							{
  3180  								Type: DiffTypeDeleted,
  3181  								Name: "GetterOptions[bar]",
  3182  								Old:  "baz",
  3183  								New:  "",
  3184  							},
  3185  							{
  3186  								Type: DiffTypeDeleted,
  3187  								Name: "GetterSource",
  3188  								Old:  "bar",
  3189  								New:  "",
  3190  							},
  3191  							{
  3192  								Type: DiffTypeDeleted,
  3193  								Name: "RelativeDest",
  3194  								Old:  "bar",
  3195  								New:  "",
  3196  							},
  3197  						},
  3198  					},
  3199  				},
  3200  			},
  3201  		},
  3202  		{
  3203  			Name: "Resources edited (no networks)",
  3204  			Old: &Task{
  3205  				Resources: &Resources{
  3206  					CPU:      100,
  3207  					MemoryMB: 100,
  3208  					DiskMB:   100,
  3209  				},
  3210  			},
  3211  			New: &Task{
  3212  				Resources: &Resources{
  3213  					CPU:      200,
  3214  					MemoryMB: 200,
  3215  					DiskMB:   200,
  3216  				},
  3217  			},
  3218  			Expected: &TaskDiff{
  3219  				Type: DiffTypeEdited,
  3220  				Objects: []*ObjectDiff{
  3221  					{
  3222  						Type: DiffTypeEdited,
  3223  						Name: "Resources",
  3224  						Fields: []*FieldDiff{
  3225  							{
  3226  								Type: DiffTypeEdited,
  3227  								Name: "CPU",
  3228  								Old:  "100",
  3229  								New:  "200",
  3230  							},
  3231  							{
  3232  								Type: DiffTypeEdited,
  3233  								Name: "DiskMB",
  3234  								Old:  "100",
  3235  								New:  "200",
  3236  							},
  3237  							{
  3238  								Type: DiffTypeEdited,
  3239  								Name: "MemoryMB",
  3240  								Old:  "100",
  3241  								New:  "200",
  3242  							},
  3243  						},
  3244  					},
  3245  				},
  3246  			},
  3247  		},
  3248  		{
  3249  			Name:       "Resources edited (no networks) with context",
  3250  			Contextual: true,
  3251  			Old: &Task{
  3252  				Resources: &Resources{
  3253  					CPU:      100,
  3254  					MemoryMB: 100,
  3255  					DiskMB:   100,
  3256  				},
  3257  			},
  3258  			New: &Task{
  3259  				Resources: &Resources{
  3260  					CPU:      200,
  3261  					MemoryMB: 100,
  3262  					DiskMB:   200,
  3263  				},
  3264  			},
  3265  			Expected: &TaskDiff{
  3266  				Type: DiffTypeEdited,
  3267  				Objects: []*ObjectDiff{
  3268  					{
  3269  						Type: DiffTypeEdited,
  3270  						Name: "Resources",
  3271  						Fields: []*FieldDiff{
  3272  							{
  3273  								Type: DiffTypeEdited,
  3274  								Name: "CPU",
  3275  								Old:  "100",
  3276  								New:  "200",
  3277  							},
  3278  							{
  3279  								Type: DiffTypeEdited,
  3280  								Name: "DiskMB",
  3281  								Old:  "100",
  3282  								New:  "200",
  3283  							},
  3284  							{
  3285  								Type: DiffTypeNone,
  3286  								Name: "IOPS",
  3287  								Old:  "0",
  3288  								New:  "0",
  3289  							},
  3290  							{
  3291  								Type: DiffTypeNone,
  3292  								Name: "MemoryMB",
  3293  								Old:  "100",
  3294  								New:  "100",
  3295  							},
  3296  						},
  3297  					},
  3298  				},
  3299  			},
  3300  		},
  3301  		{
  3302  			Name: "Network Resources edited",
  3303  			Old: &Task{
  3304  				Resources: &Resources{
  3305  					Networks: []*NetworkResource{
  3306  						{
  3307  							Device: "foo",
  3308  							CIDR:   "foo",
  3309  							IP:     "foo",
  3310  							MBits:  100,
  3311  							ReservedPorts: []Port{
  3312  								{
  3313  									Label: "foo",
  3314  									Value: 80,
  3315  								},
  3316  							},
  3317  							DynamicPorts: []Port{
  3318  								{
  3319  									Label: "bar",
  3320  								},
  3321  							},
  3322  						},
  3323  					},
  3324  				},
  3325  			},
  3326  			New: &Task{
  3327  				Resources: &Resources{
  3328  					Networks: []*NetworkResource{
  3329  						{
  3330  							Device: "bar",
  3331  							CIDR:   "bar",
  3332  							IP:     "bar",
  3333  							MBits:  200,
  3334  							ReservedPorts: []Port{
  3335  								{
  3336  									Label: "foo",
  3337  									Value: 81,
  3338  								},
  3339  							},
  3340  							DynamicPorts: []Port{
  3341  								{
  3342  									Label: "baz",
  3343  								},
  3344  							},
  3345  						},
  3346  					},
  3347  				},
  3348  			},
  3349  			Expected: &TaskDiff{
  3350  				Type: DiffTypeEdited,
  3351  				Objects: []*ObjectDiff{
  3352  					{
  3353  						Type: DiffTypeEdited,
  3354  						Name: "Resources",
  3355  						Objects: []*ObjectDiff{
  3356  							{
  3357  								Type: DiffTypeAdded,
  3358  								Name: "Network",
  3359  								Fields: []*FieldDiff{
  3360  									{
  3361  										Type: DiffTypeAdded,
  3362  										Name: "MBits",
  3363  										Old:  "",
  3364  										New:  "200",
  3365  									},
  3366  								},
  3367  								Objects: []*ObjectDiff{
  3368  									{
  3369  										Type: DiffTypeAdded,
  3370  										Name: "Static Port",
  3371  										Fields: []*FieldDiff{
  3372  											{
  3373  												Type: DiffTypeAdded,
  3374  												Name: "Label",
  3375  												Old:  "",
  3376  												New:  "foo",
  3377  											},
  3378  											{
  3379  												Type: DiffTypeAdded,
  3380  												Name: "Value",
  3381  												Old:  "",
  3382  												New:  "81",
  3383  											},
  3384  										},
  3385  									},
  3386  									{
  3387  										Type: DiffTypeAdded,
  3388  										Name: "Dynamic Port",
  3389  										Fields: []*FieldDiff{
  3390  											{
  3391  												Type: DiffTypeAdded,
  3392  												Name: "Label",
  3393  												Old:  "",
  3394  												New:  "baz",
  3395  											},
  3396  										},
  3397  									},
  3398  								},
  3399  							},
  3400  							{
  3401  								Type: DiffTypeDeleted,
  3402  								Name: "Network",
  3403  								Fields: []*FieldDiff{
  3404  									{
  3405  										Type: DiffTypeDeleted,
  3406  										Name: "MBits",
  3407  										Old:  "100",
  3408  										New:  "",
  3409  									},
  3410  								},
  3411  								Objects: []*ObjectDiff{
  3412  									{
  3413  										Type: DiffTypeDeleted,
  3414  										Name: "Static Port",
  3415  										Fields: []*FieldDiff{
  3416  											{
  3417  												Type: DiffTypeDeleted,
  3418  												Name: "Label",
  3419  												Old:  "foo",
  3420  												New:  "",
  3421  											},
  3422  											{
  3423  												Type: DiffTypeDeleted,
  3424  												Name: "Value",
  3425  												Old:  "80",
  3426  												New:  "",
  3427  											},
  3428  										},
  3429  									},
  3430  									{
  3431  										Type: DiffTypeDeleted,
  3432  										Name: "Dynamic Port",
  3433  										Fields: []*FieldDiff{
  3434  											{
  3435  												Type: DiffTypeDeleted,
  3436  												Name: "Label",
  3437  												Old:  "bar",
  3438  												New:  "",
  3439  											},
  3440  										},
  3441  									},
  3442  								},
  3443  							},
  3444  						},
  3445  					},
  3446  				},
  3447  			},
  3448  		},
  3449  		{
  3450  			Name: "Device Resources edited",
  3451  			Old: &Task{
  3452  				Resources: &Resources{
  3453  					Devices: []*RequestedDevice{
  3454  						{
  3455  							Name:  "foo",
  3456  							Count: 2,
  3457  						},
  3458  						{
  3459  							Name:  "bar",
  3460  							Count: 2,
  3461  						},
  3462  						{
  3463  							Name:  "baz",
  3464  							Count: 2,
  3465  						},
  3466  					},
  3467  				},
  3468  			},
  3469  			New: &Task{
  3470  				Resources: &Resources{
  3471  					Devices: []*RequestedDevice{
  3472  						{
  3473  							Name:  "foo",
  3474  							Count: 2,
  3475  						},
  3476  						{
  3477  							Name:  "bar",
  3478  							Count: 3,
  3479  						},
  3480  						{
  3481  							Name:  "bam",
  3482  							Count: 2,
  3483  						},
  3484  					},
  3485  				},
  3486  			},
  3487  			Expected: &TaskDiff{
  3488  				Type: DiffTypeEdited,
  3489  				Objects: []*ObjectDiff{
  3490  					{
  3491  						Type: DiffTypeEdited,
  3492  						Name: "Resources",
  3493  						Objects: []*ObjectDiff{
  3494  							{
  3495  								Type: DiffTypeEdited,
  3496  								Name: "Device",
  3497  								Fields: []*FieldDiff{
  3498  									{
  3499  										Type: DiffTypeEdited,
  3500  										Name: "Count",
  3501  										Old:  "2",
  3502  										New:  "3",
  3503  									},
  3504  								},
  3505  							},
  3506  							{
  3507  								Type: DiffTypeAdded,
  3508  								Name: "Device",
  3509  								Fields: []*FieldDiff{
  3510  									{
  3511  										Type: DiffTypeAdded,
  3512  										Name: "Count",
  3513  										Old:  "",
  3514  										New:  "2",
  3515  									},
  3516  									{
  3517  										Type: DiffTypeAdded,
  3518  										Name: "Name",
  3519  										Old:  "",
  3520  										New:  "bam",
  3521  									},
  3522  								},
  3523  							},
  3524  							{
  3525  								Type: DiffTypeDeleted,
  3526  								Name: "Device",
  3527  								Fields: []*FieldDiff{
  3528  									{
  3529  										Type: DiffTypeDeleted,
  3530  										Name: "Count",
  3531  										Old:  "2",
  3532  										New:  "",
  3533  									},
  3534  									{
  3535  										Type: DiffTypeDeleted,
  3536  										Name: "Name",
  3537  										Old:  "baz",
  3538  										New:  "",
  3539  									},
  3540  								},
  3541  							},
  3542  						},
  3543  					},
  3544  				},
  3545  			},
  3546  		},
  3547  		{
  3548  			Name:       "Device Resources edited with context",
  3549  			Contextual: true,
  3550  			Old: &Task{
  3551  				Resources: &Resources{
  3552  					CPU:      100,
  3553  					MemoryMB: 100,
  3554  					DiskMB:   100,
  3555  					Devices: []*RequestedDevice{
  3556  						{
  3557  							Name:  "foo",
  3558  							Count: 2,
  3559  						},
  3560  						{
  3561  							Name:  "bar",
  3562  							Count: 2,
  3563  						},
  3564  						{
  3565  							Name:  "baz",
  3566  							Count: 2,
  3567  						},
  3568  					},
  3569  				},
  3570  			},
  3571  			New: &Task{
  3572  				Resources: &Resources{
  3573  					CPU:      100,
  3574  					MemoryMB: 100,
  3575  					DiskMB:   100,
  3576  					Devices: []*RequestedDevice{
  3577  						{
  3578  							Name:  "foo",
  3579  							Count: 2,
  3580  						},
  3581  						{
  3582  							Name:  "bar",
  3583  							Count: 3,
  3584  						},
  3585  						{
  3586  							Name:  "bam",
  3587  							Count: 2,
  3588  						},
  3589  					},
  3590  				},
  3591  			},
  3592  			Expected: &TaskDiff{
  3593  				Type: DiffTypeEdited,
  3594  				Objects: []*ObjectDiff{
  3595  					{
  3596  						Type: DiffTypeEdited,
  3597  						Name: "Resources",
  3598  						Fields: []*FieldDiff{
  3599  							{
  3600  								Type: DiffTypeNone,
  3601  								Name: "CPU",
  3602  								Old:  "100",
  3603  								New:  "100",
  3604  							},
  3605  							{
  3606  								Type: DiffTypeNone,
  3607  								Name: "DiskMB",
  3608  								Old:  "100",
  3609  								New:  "100",
  3610  							},
  3611  							{
  3612  								Type: DiffTypeNone,
  3613  								Name: "IOPS",
  3614  								Old:  "0",
  3615  								New:  "0",
  3616  							},
  3617  							{
  3618  								Type: DiffTypeNone,
  3619  								Name: "MemoryMB",
  3620  								Old:  "100",
  3621  								New:  "100",
  3622  							},
  3623  						},
  3624  						Objects: []*ObjectDiff{
  3625  							{
  3626  								Type: DiffTypeEdited,
  3627  								Name: "Device",
  3628  								Fields: []*FieldDiff{
  3629  									{
  3630  										Type: DiffTypeEdited,
  3631  										Name: "Count",
  3632  										Old:  "2",
  3633  										New:  "3",
  3634  									},
  3635  									{
  3636  										Type: DiffTypeNone,
  3637  										Name: "Name",
  3638  										Old:  "bar",
  3639  										New:  "bar",
  3640  									},
  3641  								},
  3642  							},
  3643  							{
  3644  								Type: DiffTypeAdded,
  3645  								Name: "Device",
  3646  								Fields: []*FieldDiff{
  3647  									{
  3648  										Type: DiffTypeAdded,
  3649  										Name: "Count",
  3650  										Old:  "",
  3651  										New:  "2",
  3652  									},
  3653  									{
  3654  										Type: DiffTypeAdded,
  3655  										Name: "Name",
  3656  										Old:  "",
  3657  										New:  "bam",
  3658  									},
  3659  								},
  3660  							},
  3661  							{
  3662  								Type: DiffTypeDeleted,
  3663  								Name: "Device",
  3664  								Fields: []*FieldDiff{
  3665  									{
  3666  										Type: DiffTypeDeleted,
  3667  										Name: "Count",
  3668  										Old:  "2",
  3669  										New:  "",
  3670  									},
  3671  									{
  3672  										Type: DiffTypeDeleted,
  3673  										Name: "Name",
  3674  										Old:  "baz",
  3675  										New:  "",
  3676  									},
  3677  								},
  3678  							},
  3679  						},
  3680  					},
  3681  				},
  3682  			},
  3683  		},
  3684  		{
  3685  			Name: "Config same",
  3686  			Old: &Task{
  3687  				Config: map[string]interface{}{
  3688  					"foo": 1,
  3689  					"bar": "bar",
  3690  					"bam": []string{"a", "b"},
  3691  					"baz": map[string]int{
  3692  						"a": 1,
  3693  						"b": 2,
  3694  					},
  3695  					"boom": &Port{
  3696  						Label: "boom_port",
  3697  					},
  3698  				},
  3699  			},
  3700  			New: &Task{
  3701  				Config: map[string]interface{}{
  3702  					"foo": 1,
  3703  					"bar": "bar",
  3704  					"bam": []string{"a", "b"},
  3705  					"baz": map[string]int{
  3706  						"a": 1,
  3707  						"b": 2,
  3708  					},
  3709  					"boom": &Port{
  3710  						Label: "boom_port",
  3711  					},
  3712  				},
  3713  			},
  3714  			Expected: &TaskDiff{
  3715  				Type: DiffTypeNone,
  3716  			},
  3717  		},
  3718  		{
  3719  			Name: "Config edited",
  3720  			Old: &Task{
  3721  				Config: map[string]interface{}{
  3722  					"foo": 1,
  3723  					"bar": "baz",
  3724  					"bam": []string{"a", "b"},
  3725  					"baz": map[string]int{
  3726  						"a": 1,
  3727  						"b": 2,
  3728  					},
  3729  					"boom": &Port{
  3730  						Label: "boom_port",
  3731  					},
  3732  				},
  3733  			},
  3734  			New: &Task{
  3735  				Config: map[string]interface{}{
  3736  					"foo": 2,
  3737  					"bar": "baz",
  3738  					"bam": []string{"a", "c", "d"},
  3739  					"baz": map[string]int{
  3740  						"b": 3,
  3741  						"c": 4,
  3742  					},
  3743  					"boom": &Port{
  3744  						Label: "boom_port2",
  3745  					},
  3746  				},
  3747  			},
  3748  			Expected: &TaskDiff{
  3749  				Type: DiffTypeEdited,
  3750  				Objects: []*ObjectDiff{
  3751  					{
  3752  						Type: DiffTypeEdited,
  3753  						Name: "Config",
  3754  						Fields: []*FieldDiff{
  3755  							{
  3756  								Type: DiffTypeEdited,
  3757  								Name: "bam[1]",
  3758  								Old:  "b",
  3759  								New:  "c",
  3760  							},
  3761  							{
  3762  								Type: DiffTypeAdded,
  3763  								Name: "bam[2]",
  3764  								Old:  "",
  3765  								New:  "d",
  3766  							},
  3767  							{
  3768  								Type: DiffTypeDeleted,
  3769  								Name: "baz[a]",
  3770  								Old:  "1",
  3771  								New:  "",
  3772  							},
  3773  							{
  3774  								Type: DiffTypeEdited,
  3775  								Name: "baz[b]",
  3776  								Old:  "2",
  3777  								New:  "3",
  3778  							},
  3779  							{
  3780  								Type: DiffTypeAdded,
  3781  								Name: "baz[c]",
  3782  								Old:  "",
  3783  								New:  "4",
  3784  							},
  3785  							{
  3786  								Type: DiffTypeEdited,
  3787  								Name: "boom.Label",
  3788  								Old:  "boom_port",
  3789  								New:  "boom_port2",
  3790  							},
  3791  							{
  3792  								Type: DiffTypeEdited,
  3793  								Name: "foo",
  3794  								Old:  "1",
  3795  								New:  "2",
  3796  							},
  3797  						},
  3798  					},
  3799  				},
  3800  			},
  3801  		},
  3802  		{
  3803  			Name:       "Config edited with context",
  3804  			Contextual: true,
  3805  			Old: &Task{
  3806  				Config: map[string]interface{}{
  3807  					"foo": 1,
  3808  					"bar": "baz",
  3809  					"bam": []string{"a", "b"},
  3810  					"baz": map[string]int{
  3811  						"a": 1,
  3812  						"b": 2,
  3813  					},
  3814  					"boom": &Port{
  3815  						Label: "boom_port",
  3816  					},
  3817  				},
  3818  			},
  3819  			New: &Task{
  3820  				Config: map[string]interface{}{
  3821  					"foo": 2,
  3822  					"bar": "baz",
  3823  					"bam": []string{"a", "c", "d"},
  3824  					"baz": map[string]int{
  3825  						"a": 1,
  3826  						"b": 2,
  3827  					},
  3828  					"boom": &Port{
  3829  						Label: "boom_port",
  3830  					},
  3831  				},
  3832  			},
  3833  			Expected: &TaskDiff{
  3834  				Type: DiffTypeEdited,
  3835  				Objects: []*ObjectDiff{
  3836  					{
  3837  						Type: DiffTypeEdited,
  3838  						Name: "Config",
  3839  						Fields: []*FieldDiff{
  3840  							{
  3841  								Type: DiffTypeNone,
  3842  								Name: "bam[0]",
  3843  								Old:  "a",
  3844  								New:  "a",
  3845  							},
  3846  							{
  3847  								Type: DiffTypeEdited,
  3848  								Name: "bam[1]",
  3849  								Old:  "b",
  3850  								New:  "c",
  3851  							},
  3852  							{
  3853  								Type: DiffTypeAdded,
  3854  								Name: "bam[2]",
  3855  								Old:  "",
  3856  								New:  "d",
  3857  							},
  3858  							{
  3859  								Type: DiffTypeNone,
  3860  								Name: "bar",
  3861  								Old:  "baz",
  3862  								New:  "baz",
  3863  							},
  3864  							{
  3865  								Type: DiffTypeNone,
  3866  								Name: "baz[a]",
  3867  								Old:  "1",
  3868  								New:  "1",
  3869  							},
  3870  							{
  3871  								Type: DiffTypeNone,
  3872  								Name: "baz[b]",
  3873  								Old:  "2",
  3874  								New:  "2",
  3875  							},
  3876  							{
  3877  								Type: DiffTypeNone,
  3878  								Name: "boom.Label",
  3879  								Old:  "boom_port",
  3880  								New:  "boom_port",
  3881  							},
  3882  							{
  3883  								Type: DiffTypeNone,
  3884  								Name: "boom.Value",
  3885  								Old:  "0",
  3886  								New:  "0",
  3887  							},
  3888  							{
  3889  								Type: DiffTypeEdited,
  3890  								Name: "foo",
  3891  								Old:  "1",
  3892  								New:  "2",
  3893  							},
  3894  						},
  3895  					},
  3896  				},
  3897  			},
  3898  		},
  3899  		{
  3900  			Name: "Services edited (no checks)",
  3901  			Old: &Task{
  3902  				Services: []*Service{
  3903  					{
  3904  						Name:      "foo",
  3905  						PortLabel: "foo",
  3906  					},
  3907  					{
  3908  						Name:      "bar",
  3909  						PortLabel: "bar",
  3910  					},
  3911  					{
  3912  						Name:      "baz",
  3913  						PortLabel: "baz",
  3914  					},
  3915  				},
  3916  			},
  3917  			New: &Task{
  3918  				Services: []*Service{
  3919  					{
  3920  						Name:      "bar",
  3921  						PortLabel: "bar",
  3922  					},
  3923  					{
  3924  						Name:      "baz",
  3925  						PortLabel: "baz2",
  3926  					},
  3927  					{
  3928  						Name:      "bam",
  3929  						PortLabel: "bam",
  3930  					},
  3931  				},
  3932  			},
  3933  			Expected: &TaskDiff{
  3934  				Type: DiffTypeEdited,
  3935  				Objects: []*ObjectDiff{
  3936  					{
  3937  						Type: DiffTypeEdited,
  3938  						Name: "Service",
  3939  						Fields: []*FieldDiff{
  3940  							{
  3941  								Type: DiffTypeEdited,
  3942  								Name: "PortLabel",
  3943  								Old:  "baz",
  3944  								New:  "baz2",
  3945  							},
  3946  						},
  3947  					},
  3948  					{
  3949  						Type: DiffTypeAdded,
  3950  						Name: "Service",
  3951  						Fields: []*FieldDiff{
  3952  							{
  3953  								Type: DiffTypeAdded,
  3954  								Name: "Name",
  3955  								Old:  "",
  3956  								New:  "bam",
  3957  							},
  3958  							{
  3959  								Type: DiffTypeAdded,
  3960  								Name: "PortLabel",
  3961  								Old:  "",
  3962  								New:  "bam",
  3963  							},
  3964  						},
  3965  					},
  3966  					{
  3967  						Type: DiffTypeDeleted,
  3968  						Name: "Service",
  3969  						Fields: []*FieldDiff{
  3970  							{
  3971  								Type: DiffTypeDeleted,
  3972  								Name: "Name",
  3973  								Old:  "foo",
  3974  								New:  "",
  3975  							},
  3976  							{
  3977  								Type: DiffTypeDeleted,
  3978  								Name: "PortLabel",
  3979  								Old:  "foo",
  3980  								New:  "",
  3981  							},
  3982  						},
  3983  					},
  3984  				},
  3985  			},
  3986  		},
  3987  		{
  3988  			Name:       "Services edited (no checks) with context",
  3989  			Contextual: true,
  3990  			Old: &Task{
  3991  				Services: []*Service{
  3992  					{
  3993  						Name:      "foo",
  3994  						PortLabel: "foo",
  3995  					},
  3996  				},
  3997  			},
  3998  			New: &Task{
  3999  				Services: []*Service{
  4000  					{
  4001  						Name:        "foo",
  4002  						PortLabel:   "bar",
  4003  						AddressMode: "driver",
  4004  					},
  4005  				},
  4006  			},
  4007  			Expected: &TaskDiff{
  4008  				Type: DiffTypeEdited,
  4009  				Objects: []*ObjectDiff{
  4010  					{
  4011  						Type: DiffTypeEdited,
  4012  						Name: "Service",
  4013  						Fields: []*FieldDiff{
  4014  							{
  4015  								Type: DiffTypeAdded,
  4016  								Name: "AddressMode",
  4017  								New:  "driver",
  4018  							},
  4019  							{
  4020  								Type: DiffTypeNone,
  4021  								Name: "Name",
  4022  								Old:  "foo",
  4023  								New:  "foo",
  4024  							},
  4025  							{
  4026  								Type: DiffTypeEdited,
  4027  								Name: "PortLabel",
  4028  								Old:  "foo",
  4029  								New:  "bar",
  4030  							},
  4031  						},
  4032  					},
  4033  				},
  4034  			},
  4035  		},
  4036  		{
  4037  			Name:       "Services tags edited (no checks) with context",
  4038  			Contextual: true,
  4039  			Old: &Task{
  4040  				Services: []*Service{
  4041  					{
  4042  						Tags:       []string{"foo", "bar"},
  4043  						CanaryTags: []string{"foo", "bar"},
  4044  					},
  4045  				},
  4046  			},
  4047  			New: &Task{
  4048  				Services: []*Service{
  4049  					{
  4050  						Tags:       []string{"bar", "bam"},
  4051  						CanaryTags: []string{"bar", "bam"},
  4052  					},
  4053  				},
  4054  			},
  4055  			Expected: &TaskDiff{
  4056  				Type: DiffTypeEdited,
  4057  				Objects: []*ObjectDiff{
  4058  					{
  4059  						Type: DiffTypeEdited,
  4060  						Name: "Service",
  4061  						Objects: []*ObjectDiff{
  4062  							{
  4063  								Type: DiffTypeEdited,
  4064  								Name: "CanaryTags",
  4065  								Fields: []*FieldDiff{
  4066  									{
  4067  										Type: DiffTypeAdded,
  4068  										Name: "CanaryTags",
  4069  										Old:  "",
  4070  										New:  "bam",
  4071  									},
  4072  									{
  4073  										Type: DiffTypeNone,
  4074  										Name: "CanaryTags",
  4075  										Old:  "bar",
  4076  										New:  "bar",
  4077  									},
  4078  									{
  4079  										Type: DiffTypeDeleted,
  4080  										Name: "CanaryTags",
  4081  										Old:  "foo",
  4082  										New:  "",
  4083  									},
  4084  								},
  4085  							},
  4086  							{
  4087  								Type: DiffTypeEdited,
  4088  								Name: "Tags",
  4089  								Fields: []*FieldDiff{
  4090  									{
  4091  										Type: DiffTypeAdded,
  4092  										Name: "Tags",
  4093  										Old:  "",
  4094  										New:  "bam",
  4095  									},
  4096  									{
  4097  										Type: DiffTypeNone,
  4098  										Name: "Tags",
  4099  										Old:  "bar",
  4100  										New:  "bar",
  4101  									},
  4102  									{
  4103  										Type: DiffTypeDeleted,
  4104  										Name: "Tags",
  4105  										Old:  "foo",
  4106  										New:  "",
  4107  									},
  4108  								},
  4109  							},
  4110  						},
  4111  						Fields: []*FieldDiff{
  4112  							{
  4113  								Type: DiffTypeNone,
  4114  								Name: "AddressMode",
  4115  							},
  4116  							{
  4117  								Type: DiffTypeNone,
  4118  								Name: "Name",
  4119  							},
  4120  							{
  4121  								Type: DiffTypeNone,
  4122  								Name: "PortLabel",
  4123  							},
  4124  						},
  4125  					},
  4126  				},
  4127  			},
  4128  		},
  4129  		{
  4130  			Name: "Service Checks edited",
  4131  			Old: &Task{
  4132  				Services: []*Service{
  4133  					{
  4134  						Name: "foo",
  4135  						Checks: []*ServiceCheck{
  4136  							{
  4137  								Name:     "foo",
  4138  								Type:     "http",
  4139  								Command:  "foo",
  4140  								Args:     []string{"foo"},
  4141  								Path:     "foo",
  4142  								Protocol: "http",
  4143  								Interval: 1 * time.Second,
  4144  								Timeout:  1 * time.Second,
  4145  								Header: map[string][]string{
  4146  									"Foo": {"bar"},
  4147  								},
  4148  							},
  4149  							{
  4150  								Name:     "bar",
  4151  								Type:     "http",
  4152  								Command:  "foo",
  4153  								Args:     []string{"foo"},
  4154  								Path:     "foo",
  4155  								Protocol: "http",
  4156  								Interval: 1 * time.Second,
  4157  								Timeout:  1 * time.Second,
  4158  							},
  4159  							{
  4160  								Name:     "baz",
  4161  								Type:     "http",
  4162  								Command:  "foo",
  4163  								Args:     []string{"foo"},
  4164  								Path:     "foo",
  4165  								Protocol: "http",
  4166  								Interval: 1 * time.Second,
  4167  								Timeout:  1 * time.Second,
  4168  							},
  4169  						},
  4170  					},
  4171  				},
  4172  			},
  4173  			New: &Task{
  4174  				Services: []*Service{
  4175  					{
  4176  						Name: "foo",
  4177  						Checks: []*ServiceCheck{
  4178  							{
  4179  								Name:     "bar",
  4180  								Type:     "http",
  4181  								Command:  "foo",
  4182  								Args:     []string{"foo"},
  4183  								Path:     "foo",
  4184  								Protocol: "http",
  4185  								Interval: 1 * time.Second,
  4186  								Timeout:  1 * time.Second,
  4187  							},
  4188  							{
  4189  								Name:     "baz",
  4190  								Type:     "tcp",
  4191  								Command:  "foo",
  4192  								Args:     []string{"foo"},
  4193  								Path:     "foo",
  4194  								Protocol: "http",
  4195  								Interval: 1 * time.Second,
  4196  								Timeout:  1 * time.Second,
  4197  								Header: map[string][]string{
  4198  									"Eggs": {"spam"},
  4199  								},
  4200  							},
  4201  							{
  4202  								Name:     "bam",
  4203  								Type:     "http",
  4204  								Command:  "foo",
  4205  								Args:     []string{"foo"},
  4206  								Path:     "foo",
  4207  								Protocol: "http",
  4208  								Interval: 1 * time.Second,
  4209  								Timeout:  1 * time.Second,
  4210  							},
  4211  						},
  4212  					},
  4213  				},
  4214  			},
  4215  			Expected: &TaskDiff{
  4216  				Type: DiffTypeEdited,
  4217  				Objects: []*ObjectDiff{
  4218  					{
  4219  						Type: DiffTypeEdited,
  4220  						Name: "Service",
  4221  						Objects: []*ObjectDiff{
  4222  							{
  4223  								Type: DiffTypeEdited,
  4224  								Name: "Check",
  4225  								Fields: []*FieldDiff{
  4226  									{
  4227  										Type: DiffTypeEdited,
  4228  										Name: "Type",
  4229  										Old:  "http",
  4230  										New:  "tcp",
  4231  									},
  4232  								},
  4233  								Objects: []*ObjectDiff{
  4234  									{
  4235  										Type: DiffTypeAdded,
  4236  										Name: "Header",
  4237  										Fields: []*FieldDiff{
  4238  											{
  4239  												Type: DiffTypeAdded,
  4240  												Name: "Eggs[0]",
  4241  												Old:  "",
  4242  												New:  "spam",
  4243  											},
  4244  										},
  4245  									},
  4246  								},
  4247  							},
  4248  							{
  4249  								Type: DiffTypeAdded,
  4250  								Name: "Check",
  4251  								Fields: []*FieldDiff{
  4252  									{
  4253  										Type: DiffTypeAdded,
  4254  										Name: "Command",
  4255  										Old:  "",
  4256  										New:  "foo",
  4257  									},
  4258  									{
  4259  										Type: DiffTypeAdded,
  4260  										Name: "GRPCUseTLS",
  4261  										Old:  "",
  4262  										New:  "false",
  4263  									},
  4264  									{
  4265  										Type: DiffTypeAdded,
  4266  										Name: "Interval",
  4267  										Old:  "",
  4268  										New:  "1000000000",
  4269  									},
  4270  									{
  4271  										Type: DiffTypeAdded,
  4272  										Name: "Name",
  4273  										Old:  "",
  4274  										New:  "bam",
  4275  									},
  4276  									{
  4277  										Type: DiffTypeAdded,
  4278  										Name: "Path",
  4279  										Old:  "",
  4280  										New:  "foo",
  4281  									},
  4282  									{
  4283  										Type: DiffTypeAdded,
  4284  										Name: "Protocol",
  4285  										Old:  "",
  4286  										New:  "http",
  4287  									},
  4288  									{
  4289  										Type: DiffTypeAdded,
  4290  										Name: "TLSSkipVerify",
  4291  										Old:  "",
  4292  										New:  "false",
  4293  									},
  4294  									{
  4295  										Type: DiffTypeAdded,
  4296  										Name: "Timeout",
  4297  										Old:  "",
  4298  										New:  "1000000000",
  4299  									},
  4300  									{
  4301  										Type: DiffTypeAdded,
  4302  										Name: "Type",
  4303  										Old:  "",
  4304  										New:  "http",
  4305  									},
  4306  								},
  4307  							},
  4308  							{
  4309  								Type: DiffTypeDeleted,
  4310  								Name: "Check",
  4311  								Fields: []*FieldDiff{
  4312  									{
  4313  										Type: DiffTypeDeleted,
  4314  										Name: "Command",
  4315  										Old:  "foo",
  4316  										New:  "",
  4317  									},
  4318  									{
  4319  										Type: DiffTypeDeleted,
  4320  										Name: "GRPCUseTLS",
  4321  										Old:  "false",
  4322  										New:  "",
  4323  									},
  4324  									{
  4325  										Type: DiffTypeDeleted,
  4326  										Name: "Interval",
  4327  										Old:  "1000000000",
  4328  										New:  "",
  4329  									},
  4330  									{
  4331  										Type: DiffTypeDeleted,
  4332  										Name: "Name",
  4333  										Old:  "foo",
  4334  										New:  "",
  4335  									},
  4336  									{
  4337  										Type: DiffTypeDeleted,
  4338  										Name: "Path",
  4339  										Old:  "foo",
  4340  										New:  "",
  4341  									},
  4342  									{
  4343  										Type: DiffTypeDeleted,
  4344  										Name: "Protocol",
  4345  										Old:  "http",
  4346  										New:  "",
  4347  									},
  4348  									{
  4349  										Type: DiffTypeDeleted,
  4350  										Name: "TLSSkipVerify",
  4351  										Old:  "false",
  4352  										New:  "",
  4353  									},
  4354  									{
  4355  										Type: DiffTypeDeleted,
  4356  										Name: "Timeout",
  4357  										Old:  "1000000000",
  4358  										New:  "",
  4359  									},
  4360  									{
  4361  										Type: DiffTypeDeleted,
  4362  										Name: "Type",
  4363  										Old:  "http",
  4364  										New:  "",
  4365  									},
  4366  								},
  4367  								Objects: []*ObjectDiff{
  4368  									{
  4369  										Type: DiffTypeDeleted,
  4370  										Name: "Header",
  4371  										Fields: []*FieldDiff{
  4372  											{
  4373  												Type: DiffTypeDeleted,
  4374  												Name: "Foo[0]",
  4375  												Old:  "bar",
  4376  											},
  4377  										},
  4378  									},
  4379  								},
  4380  							},
  4381  						},
  4382  					},
  4383  				},
  4384  			},
  4385  		},
  4386  		{
  4387  			Name:       "Service Checks edited with context",
  4388  			Contextual: true,
  4389  			Old: &Task{
  4390  				Services: []*Service{
  4391  					{
  4392  						Name: "foo",
  4393  						Checks: []*ServiceCheck{
  4394  							{
  4395  								Name:          "foo",
  4396  								Type:          "http",
  4397  								Command:       "foo",
  4398  								Args:          []string{"foo"},
  4399  								Path:          "foo",
  4400  								Protocol:      "http",
  4401  								Interval:      1 * time.Second,
  4402  								Timeout:       1 * time.Second,
  4403  								InitialStatus: "critical",
  4404  								Header: map[string][]string{
  4405  									"Foo": {"bar"},
  4406  								},
  4407  							},
  4408  						},
  4409  					},
  4410  				},
  4411  			},
  4412  			New: &Task{
  4413  				Services: []*Service{
  4414  					{
  4415  						Name: "foo",
  4416  						Checks: []*ServiceCheck{
  4417  							{
  4418  								Name:          "foo",
  4419  								Type:          "tcp",
  4420  								Command:       "foo",
  4421  								Args:          []string{"foo"},
  4422  								Path:          "foo",
  4423  								Protocol:      "http",
  4424  								Interval:      1 * time.Second,
  4425  								Timeout:       1 * time.Second,
  4426  								InitialStatus: "passing",
  4427  								Method:        "POST",
  4428  								Header: map[string][]string{
  4429  									"Foo":  {"bar", "baz"},
  4430  									"Eggs": {"spam"},
  4431  								},
  4432  							},
  4433  						},
  4434  					},
  4435  				},
  4436  			},
  4437  			Expected: &TaskDiff{
  4438  				Type: DiffTypeEdited,
  4439  				Objects: []*ObjectDiff{
  4440  					{
  4441  						Type: DiffTypeEdited,
  4442  						Name: "Service",
  4443  						Fields: []*FieldDiff{
  4444  							{
  4445  								Type: DiffTypeNone,
  4446  								Name: "AddressMode",
  4447  								Old:  "",
  4448  								New:  "",
  4449  							},
  4450  							{
  4451  								Type: DiffTypeNone,
  4452  								Name: "Name",
  4453  								Old:  "foo",
  4454  								New:  "foo",
  4455  							},
  4456  							{
  4457  								Type: DiffTypeNone,
  4458  								Name: "PortLabel",
  4459  								Old:  "",
  4460  								New:  "",
  4461  							},
  4462  						},
  4463  						Objects: []*ObjectDiff{
  4464  							{
  4465  								Type: DiffTypeEdited,
  4466  								Name: "Check",
  4467  								Fields: []*FieldDiff{
  4468  									{
  4469  										Type: DiffTypeNone,
  4470  										Name: "AddressMode",
  4471  										Old:  "",
  4472  										New:  "",
  4473  									},
  4474  									{
  4475  										Type: DiffTypeNone,
  4476  										Name: "Command",
  4477  										Old:  "foo",
  4478  										New:  "foo",
  4479  									},
  4480  									{
  4481  										Type: DiffTypeNone,
  4482  										Name: "GRPCService",
  4483  										Old:  "",
  4484  										New:  "",
  4485  									},
  4486  									{
  4487  										Type: DiffTypeNone,
  4488  										Name: "GRPCUseTLS",
  4489  										Old:  "false",
  4490  										New:  "false",
  4491  									},
  4492  									{
  4493  										Type: DiffTypeEdited,
  4494  										Name: "InitialStatus",
  4495  										Old:  "critical",
  4496  										New:  "passing",
  4497  									},
  4498  									{
  4499  										Type: DiffTypeNone,
  4500  										Name: "Interval",
  4501  										Old:  "1000000000",
  4502  										New:  "1000000000",
  4503  									},
  4504  									{
  4505  										Type: DiffTypeAdded,
  4506  										Name: "Method",
  4507  										Old:  "",
  4508  										New:  "POST",
  4509  									},
  4510  									{
  4511  										Type: DiffTypeNone,
  4512  										Name: "Name",
  4513  										Old:  "foo",
  4514  										New:  "foo",
  4515  									},
  4516  									{
  4517  										Type: DiffTypeNone,
  4518  										Name: "Path",
  4519  										Old:  "foo",
  4520  										New:  "foo",
  4521  									},
  4522  									{
  4523  										Type: DiffTypeNone,
  4524  										Name: "PortLabel",
  4525  										Old:  "",
  4526  										New:  "",
  4527  									},
  4528  									{
  4529  										Type: DiffTypeNone,
  4530  										Name: "Protocol",
  4531  										Old:  "http",
  4532  										New:  "http",
  4533  									},
  4534  									{
  4535  										Type: DiffTypeNone,
  4536  										Name: "TLSSkipVerify",
  4537  										Old:  "false",
  4538  										New:  "false",
  4539  									},
  4540  									{
  4541  										Type: DiffTypeNone,
  4542  										Name: "Timeout",
  4543  										Old:  "1000000000",
  4544  										New:  "1000000000",
  4545  									},
  4546  									{
  4547  										Type: DiffTypeEdited,
  4548  										Name: "Type",
  4549  										Old:  "http",
  4550  										New:  "tcp",
  4551  									},
  4552  								},
  4553  								Objects: []*ObjectDiff{
  4554  									{
  4555  										Type: DiffTypeEdited,
  4556  										Name: "Header",
  4557  										Fields: []*FieldDiff{
  4558  											{
  4559  												Type: DiffTypeAdded,
  4560  												Name: "Eggs[0]",
  4561  												Old:  "",
  4562  												New:  "spam",
  4563  											},
  4564  											{
  4565  												Type: DiffTypeNone,
  4566  												Name: "Foo[0]",
  4567  												Old:  "bar",
  4568  												New:  "bar",
  4569  											},
  4570  											{
  4571  												Type: DiffTypeAdded,
  4572  												Name: "Foo[1]",
  4573  												Old:  "",
  4574  												New:  "baz",
  4575  											},
  4576  										},
  4577  									},
  4578  								},
  4579  							},
  4580  						},
  4581  					},
  4582  				},
  4583  			},
  4584  		},
  4585  		{
  4586  			Name: "CheckRestart edited",
  4587  			Old: &Task{
  4588  				Services: []*Service{
  4589  					{
  4590  						Name: "foo",
  4591  						Checks: []*ServiceCheck{
  4592  							{
  4593  								Name:     "foo",
  4594  								Type:     "http",
  4595  								Command:  "foo",
  4596  								Args:     []string{"foo"},
  4597  								Path:     "foo",
  4598  								Protocol: "http",
  4599  								Interval: 1 * time.Second,
  4600  								Timeout:  1 * time.Second,
  4601  							},
  4602  							{
  4603  								Name:     "bar",
  4604  								Type:     "http",
  4605  								Command:  "foo",
  4606  								Args:     []string{"foo"},
  4607  								Path:     "foo",
  4608  								Protocol: "http",
  4609  								Interval: 1 * time.Second,
  4610  								Timeout:  1 * time.Second,
  4611  								CheckRestart: &CheckRestart{
  4612  									Limit: 2,
  4613  									Grace: 2 * time.Second,
  4614  								},
  4615  							},
  4616  							{
  4617  								Name:     "baz",
  4618  								Type:     "http",
  4619  								Command:  "foo",
  4620  								Args:     []string{"foo"},
  4621  								Path:     "foo",
  4622  								Protocol: "http",
  4623  								Interval: 1 * time.Second,
  4624  								Timeout:  1 * time.Second,
  4625  								CheckRestart: &CheckRestart{
  4626  									Limit: 3,
  4627  									Grace: 3 * time.Second,
  4628  								},
  4629  							},
  4630  						},
  4631  					},
  4632  				},
  4633  			},
  4634  			New: &Task{
  4635  				Services: []*Service{
  4636  					{
  4637  						Name: "foo",
  4638  						Checks: []*ServiceCheck{
  4639  							{
  4640  								Name:     "foo",
  4641  								Type:     "http",
  4642  								Command:  "foo",
  4643  								Args:     []string{"foo"},
  4644  								Path:     "foo",
  4645  								Protocol: "http",
  4646  								Interval: 1 * time.Second,
  4647  								Timeout:  1 * time.Second,
  4648  								CheckRestart: &CheckRestart{
  4649  									Limit: 1,
  4650  									Grace: 1 * time.Second,
  4651  								},
  4652  							},
  4653  							{
  4654  								Name:     "bar",
  4655  								Type:     "http",
  4656  								Command:  "foo",
  4657  								Args:     []string{"foo"},
  4658  								Path:     "foo",
  4659  								Protocol: "http",
  4660  								Interval: 1 * time.Second,
  4661  								Timeout:  1 * time.Second,
  4662  							},
  4663  							{
  4664  								Name:     "baz",
  4665  								Type:     "http",
  4666  								Command:  "foo",
  4667  								Args:     []string{"foo"},
  4668  								Path:     "foo",
  4669  								Protocol: "http",
  4670  								Interval: 1 * time.Second,
  4671  								Timeout:  1 * time.Second,
  4672  								CheckRestart: &CheckRestart{
  4673  									Limit: 4,
  4674  									Grace: 4 * time.Second,
  4675  								},
  4676  							},
  4677  						},
  4678  					},
  4679  				},
  4680  			},
  4681  			Expected: &TaskDiff{
  4682  				Type: DiffTypeEdited,
  4683  				Objects: []*ObjectDiff{
  4684  					{
  4685  						Type: DiffTypeEdited,
  4686  						Name: "Service",
  4687  						Objects: []*ObjectDiff{
  4688  							{
  4689  								Type: DiffTypeEdited,
  4690  								Name: "Check",
  4691  								Objects: []*ObjectDiff{
  4692  									{
  4693  										Type: DiffTypeEdited,
  4694  										Name: "CheckRestart",
  4695  										Fields: []*FieldDiff{
  4696  											{
  4697  												Type: DiffTypeEdited,
  4698  												Name: "Grace",
  4699  												Old:  "3000000000",
  4700  												New:  "4000000000",
  4701  											},
  4702  											{
  4703  												Type: DiffTypeEdited,
  4704  												Name: "Limit",
  4705  												Old:  "3",
  4706  												New:  "4",
  4707  											},
  4708  										},
  4709  									},
  4710  								},
  4711  							},
  4712  							{
  4713  								Type: DiffTypeEdited,
  4714  								Name: "Check",
  4715  								Objects: []*ObjectDiff{
  4716  									{
  4717  										Type: DiffTypeAdded,
  4718  										Name: "CheckRestart",
  4719  										Fields: []*FieldDiff{
  4720  											{
  4721  												Type: DiffTypeAdded,
  4722  												Name: "Grace",
  4723  												New:  "1000000000",
  4724  											},
  4725  											{
  4726  												Type: DiffTypeAdded,
  4727  												Name: "IgnoreWarnings",
  4728  												New:  "false",
  4729  											},
  4730  											{
  4731  												Type: DiffTypeAdded,
  4732  												Name: "Limit",
  4733  												New:  "1",
  4734  											},
  4735  										},
  4736  									},
  4737  								},
  4738  							},
  4739  							{
  4740  								Type: DiffTypeEdited,
  4741  								Name: "Check",
  4742  								Objects: []*ObjectDiff{
  4743  									{
  4744  										Type: DiffTypeDeleted,
  4745  										Name: "CheckRestart",
  4746  										Fields: []*FieldDiff{
  4747  											{
  4748  												Type: DiffTypeDeleted,
  4749  												Name: "Grace",
  4750  												Old:  "2000000000",
  4751  											},
  4752  											{
  4753  												Type: DiffTypeDeleted,
  4754  												Name: "IgnoreWarnings",
  4755  												Old:  "false",
  4756  											},
  4757  											{
  4758  												Type: DiffTypeDeleted,
  4759  												Name: "Limit",
  4760  												Old:  "2",
  4761  											},
  4762  										},
  4763  									},
  4764  								},
  4765  							},
  4766  						},
  4767  					},
  4768  				},
  4769  			},
  4770  		},
  4771  		{
  4772  			Name: "Vault added",
  4773  			Old:  &Task{},
  4774  			New: &Task{
  4775  				Vault: &Vault{
  4776  					Policies:     []string{"foo", "bar"},
  4777  					Env:          true,
  4778  					ChangeMode:   "signal",
  4779  					ChangeSignal: "SIGUSR1",
  4780  				},
  4781  			},
  4782  			Expected: &TaskDiff{
  4783  				Type: DiffTypeEdited,
  4784  				Objects: []*ObjectDiff{
  4785  					{
  4786  						Type: DiffTypeAdded,
  4787  						Name: "Vault",
  4788  						Fields: []*FieldDiff{
  4789  							{
  4790  								Type: DiffTypeAdded,
  4791  								Name: "ChangeMode",
  4792  								Old:  "",
  4793  								New:  "signal",
  4794  							},
  4795  							{
  4796  								Type: DiffTypeAdded,
  4797  								Name: "ChangeSignal",
  4798  								Old:  "",
  4799  								New:  "SIGUSR1",
  4800  							},
  4801  							{
  4802  								Type: DiffTypeAdded,
  4803  								Name: "Env",
  4804  								Old:  "",
  4805  								New:  "true",
  4806  							},
  4807  						},
  4808  						Objects: []*ObjectDiff{
  4809  							{
  4810  								Type: DiffTypeAdded,
  4811  								Name: "Policies",
  4812  								Fields: []*FieldDiff{
  4813  									{
  4814  										Type: DiffTypeAdded,
  4815  										Name: "Policies",
  4816  										Old:  "",
  4817  										New:  "bar",
  4818  									},
  4819  									{
  4820  										Type: DiffTypeAdded,
  4821  										Name: "Policies",
  4822  										Old:  "",
  4823  										New:  "foo",
  4824  									},
  4825  								},
  4826  							},
  4827  						},
  4828  					},
  4829  				},
  4830  			},
  4831  		},
  4832  		{
  4833  			Name: "Vault deleted",
  4834  			Old: &Task{
  4835  				Vault: &Vault{
  4836  					Policies:     []string{"foo", "bar"},
  4837  					Env:          true,
  4838  					ChangeMode:   "signal",
  4839  					ChangeSignal: "SIGUSR1",
  4840  				},
  4841  			},
  4842  			New: &Task{},
  4843  			Expected: &TaskDiff{
  4844  				Type: DiffTypeEdited,
  4845  				Objects: []*ObjectDiff{
  4846  					{
  4847  						Type: DiffTypeDeleted,
  4848  						Name: "Vault",
  4849  						Fields: []*FieldDiff{
  4850  							{
  4851  								Type: DiffTypeDeleted,
  4852  								Name: "ChangeMode",
  4853  								Old:  "signal",
  4854  								New:  "",
  4855  							},
  4856  							{
  4857  								Type: DiffTypeDeleted,
  4858  								Name: "ChangeSignal",
  4859  								Old:  "SIGUSR1",
  4860  								New:  "",
  4861  							},
  4862  							{
  4863  								Type: DiffTypeDeleted,
  4864  								Name: "Env",
  4865  								Old:  "true",
  4866  								New:  "",
  4867  							},
  4868  						},
  4869  						Objects: []*ObjectDiff{
  4870  							{
  4871  								Type: DiffTypeDeleted,
  4872  								Name: "Policies",
  4873  								Fields: []*FieldDiff{
  4874  									{
  4875  										Type: DiffTypeDeleted,
  4876  										Name: "Policies",
  4877  										Old:  "bar",
  4878  										New:  "",
  4879  									},
  4880  									{
  4881  										Type: DiffTypeDeleted,
  4882  										Name: "Policies",
  4883  										Old:  "foo",
  4884  										New:  "",
  4885  									},
  4886  								},
  4887  							},
  4888  						},
  4889  					},
  4890  				},
  4891  			},
  4892  		},
  4893  		{
  4894  			Name: "Vault edited",
  4895  			Old: &Task{
  4896  				Vault: &Vault{
  4897  					Policies:     []string{"foo", "bar"},
  4898  					Env:          true,
  4899  					ChangeMode:   "signal",
  4900  					ChangeSignal: "SIGUSR1",
  4901  				},
  4902  			},
  4903  			New: &Task{
  4904  				Vault: &Vault{
  4905  					Policies:     []string{"bar", "baz"},
  4906  					Env:          false,
  4907  					ChangeMode:   "restart",
  4908  					ChangeSignal: "foo",
  4909  				},
  4910  			},
  4911  			Expected: &TaskDiff{
  4912  				Type: DiffTypeEdited,
  4913  				Objects: []*ObjectDiff{
  4914  					{
  4915  						Type: DiffTypeEdited,
  4916  						Name: "Vault",
  4917  						Fields: []*FieldDiff{
  4918  							{
  4919  								Type: DiffTypeEdited,
  4920  								Name: "ChangeMode",
  4921  								Old:  "signal",
  4922  								New:  "restart",
  4923  							},
  4924  							{
  4925  								Type: DiffTypeEdited,
  4926  								Name: "ChangeSignal",
  4927  								Old:  "SIGUSR1",
  4928  								New:  "foo",
  4929  							},
  4930  							{
  4931  								Type: DiffTypeEdited,
  4932  								Name: "Env",
  4933  								Old:  "true",
  4934  								New:  "false",
  4935  							},
  4936  						},
  4937  						Objects: []*ObjectDiff{
  4938  							{
  4939  								Type: DiffTypeEdited,
  4940  								Name: "Policies",
  4941  								Fields: []*FieldDiff{
  4942  									{
  4943  										Type: DiffTypeAdded,
  4944  										Name: "Policies",
  4945  										Old:  "",
  4946  										New:  "baz",
  4947  									},
  4948  									{
  4949  										Type: DiffTypeDeleted,
  4950  										Name: "Policies",
  4951  										Old:  "foo",
  4952  										New:  "",
  4953  									},
  4954  								},
  4955  							},
  4956  						},
  4957  					},
  4958  				},
  4959  			},
  4960  		},
  4961  		{
  4962  			Name:       "Vault edited with context",
  4963  			Contextual: true,
  4964  			Old: &Task{
  4965  				Vault: &Vault{
  4966  					Policies:     []string{"foo", "bar"},
  4967  					Env:          true,
  4968  					ChangeMode:   "signal",
  4969  					ChangeSignal: "SIGUSR1",
  4970  				},
  4971  			},
  4972  			New: &Task{
  4973  				Vault: &Vault{
  4974  					Policies:     []string{"bar", "baz"},
  4975  					Env:          true,
  4976  					ChangeMode:   "signal",
  4977  					ChangeSignal: "SIGUSR1",
  4978  				},
  4979  			},
  4980  			Expected: &TaskDiff{
  4981  				Type: DiffTypeEdited,
  4982  				Objects: []*ObjectDiff{
  4983  					{
  4984  						Type: DiffTypeEdited,
  4985  						Name: "Vault",
  4986  						Fields: []*FieldDiff{
  4987  							{
  4988  								Type: DiffTypeNone,
  4989  								Name: "ChangeMode",
  4990  								Old:  "signal",
  4991  								New:  "signal",
  4992  							},
  4993  							{
  4994  								Type: DiffTypeNone,
  4995  								Name: "ChangeSignal",
  4996  								Old:  "SIGUSR1",
  4997  								New:  "SIGUSR1",
  4998  							},
  4999  							{
  5000  								Type: DiffTypeNone,
  5001  								Name: "Env",
  5002  								Old:  "true",
  5003  								New:  "true",
  5004  							},
  5005  						},
  5006  						Objects: []*ObjectDiff{
  5007  							{
  5008  								Type: DiffTypeEdited,
  5009  								Name: "Policies",
  5010  								Fields: []*FieldDiff{
  5011  									{
  5012  										Type: DiffTypeAdded,
  5013  										Name: "Policies",
  5014  										Old:  "",
  5015  										New:  "baz",
  5016  									},
  5017  									{
  5018  										Type: DiffTypeNone,
  5019  										Name: "Policies",
  5020  										Old:  "bar",
  5021  										New:  "bar",
  5022  									},
  5023  									{
  5024  										Type: DiffTypeDeleted,
  5025  										Name: "Policies",
  5026  										Old:  "foo",
  5027  										New:  "",
  5028  									},
  5029  								},
  5030  							},
  5031  						},
  5032  					},
  5033  				},
  5034  			},
  5035  		},
  5036  		{
  5037  			Name: "Template edited",
  5038  			Old: &Task{
  5039  				Templates: []*Template{
  5040  					{
  5041  						SourcePath:   "foo",
  5042  						DestPath:     "bar",
  5043  						EmbeddedTmpl: "baz",
  5044  						ChangeMode:   "bam",
  5045  						ChangeSignal: "SIGHUP",
  5046  						Splay:        1,
  5047  						Perms:        "0644",
  5048  						VaultGrace:   3 * time.Second,
  5049  					},
  5050  					{
  5051  						SourcePath:   "foo2",
  5052  						DestPath:     "bar2",
  5053  						EmbeddedTmpl: "baz2",
  5054  						ChangeMode:   "bam2",
  5055  						ChangeSignal: "SIGHUP2",
  5056  						Splay:        2,
  5057  						Perms:        "0666",
  5058  						Envvars:      true,
  5059  						VaultGrace:   5 * time.Second,
  5060  					},
  5061  				},
  5062  			},
  5063  			New: &Task{
  5064  				Templates: []*Template{
  5065  					{
  5066  						SourcePath:   "foo",
  5067  						DestPath:     "bar",
  5068  						EmbeddedTmpl: "baz",
  5069  						ChangeMode:   "bam",
  5070  						ChangeSignal: "SIGHUP",
  5071  						Splay:        1,
  5072  						Perms:        "0644",
  5073  						VaultGrace:   3 * time.Second,
  5074  					},
  5075  					{
  5076  						SourcePath:   "foo3",
  5077  						DestPath:     "bar3",
  5078  						EmbeddedTmpl: "baz3",
  5079  						ChangeMode:   "bam3",
  5080  						ChangeSignal: "SIGHUP3",
  5081  						Splay:        3,
  5082  						Perms:        "0776",
  5083  						VaultGrace:   10 * time.Second,
  5084  					},
  5085  				},
  5086  			},
  5087  			Expected: &TaskDiff{
  5088  				Type: DiffTypeEdited,
  5089  				Objects: []*ObjectDiff{
  5090  					{
  5091  						Type: DiffTypeAdded,
  5092  						Name: "Template",
  5093  						Fields: []*FieldDiff{
  5094  							{
  5095  								Type: DiffTypeAdded,
  5096  								Name: "ChangeMode",
  5097  								Old:  "",
  5098  								New:  "bam3",
  5099  							},
  5100  							{
  5101  								Type: DiffTypeAdded,
  5102  								Name: "ChangeSignal",
  5103  								Old:  "",
  5104  								New:  "SIGHUP3",
  5105  							},
  5106  							{
  5107  								Type: DiffTypeAdded,
  5108  								Name: "DestPath",
  5109  								Old:  "",
  5110  								New:  "bar3",
  5111  							},
  5112  							{
  5113  								Type: DiffTypeAdded,
  5114  								Name: "EmbeddedTmpl",
  5115  								Old:  "",
  5116  								New:  "baz3",
  5117  							},
  5118  							{
  5119  								Type: DiffTypeAdded,
  5120  								Name: "Envvars",
  5121  								Old:  "",
  5122  								New:  "false",
  5123  							},
  5124  							{
  5125  								Type: DiffTypeAdded,
  5126  								Name: "Perms",
  5127  								Old:  "",
  5128  								New:  "0776",
  5129  							},
  5130  							{
  5131  								Type: DiffTypeAdded,
  5132  								Name: "SourcePath",
  5133  								Old:  "",
  5134  								New:  "foo3",
  5135  							},
  5136  							{
  5137  								Type: DiffTypeAdded,
  5138  								Name: "Splay",
  5139  								Old:  "",
  5140  								New:  "3",
  5141  							},
  5142  							{
  5143  								Type: DiffTypeAdded,
  5144  								Name: "VaultGrace",
  5145  								Old:  "",
  5146  								New:  "10000000000",
  5147  							},
  5148  						},
  5149  					},
  5150  					{
  5151  						Type: DiffTypeDeleted,
  5152  						Name: "Template",
  5153  						Fields: []*FieldDiff{
  5154  							{
  5155  								Type: DiffTypeDeleted,
  5156  								Name: "ChangeMode",
  5157  								Old:  "bam2",
  5158  								New:  "",
  5159  							},
  5160  							{
  5161  								Type: DiffTypeDeleted,
  5162  								Name: "ChangeSignal",
  5163  								Old:  "SIGHUP2",
  5164  								New:  "",
  5165  							},
  5166  							{
  5167  								Type: DiffTypeDeleted,
  5168  								Name: "DestPath",
  5169  								Old:  "bar2",
  5170  								New:  "",
  5171  							},
  5172  							{
  5173  								Type: DiffTypeDeleted,
  5174  								Name: "EmbeddedTmpl",
  5175  								Old:  "baz2",
  5176  								New:  "",
  5177  							},
  5178  							{
  5179  								Type: DiffTypeDeleted,
  5180  								Name: "Envvars",
  5181  								Old:  "true",
  5182  								New:  "",
  5183  							},
  5184  							{
  5185  								Type: DiffTypeDeleted,
  5186  								Name: "Perms",
  5187  								Old:  "0666",
  5188  								New:  "",
  5189  							},
  5190  							{
  5191  								Type: DiffTypeDeleted,
  5192  								Name: "SourcePath",
  5193  								Old:  "foo2",
  5194  								New:  "",
  5195  							},
  5196  							{
  5197  								Type: DiffTypeDeleted,
  5198  								Name: "Splay",
  5199  								Old:  "2",
  5200  								New:  "",
  5201  							},
  5202  							{
  5203  								Type: DiffTypeDeleted,
  5204  								Name: "VaultGrace",
  5205  								Old:  "5000000000",
  5206  								New:  "",
  5207  							},
  5208  						},
  5209  					},
  5210  				},
  5211  			},
  5212  		},
  5213  		{
  5214  			Name: "DispatchPayload added",
  5215  			Old:  &Task{},
  5216  			New: &Task{
  5217  				DispatchPayload: &DispatchPayloadConfig{
  5218  					File: "foo",
  5219  				},
  5220  			},
  5221  			Expected: &TaskDiff{
  5222  				Type: DiffTypeEdited,
  5223  				Objects: []*ObjectDiff{
  5224  					{
  5225  						Type: DiffTypeAdded,
  5226  						Name: "DispatchPayload",
  5227  						Fields: []*FieldDiff{
  5228  							{
  5229  								Type: DiffTypeAdded,
  5230  								Name: "File",
  5231  								Old:  "",
  5232  								New:  "foo",
  5233  							},
  5234  						},
  5235  					},
  5236  				},
  5237  			},
  5238  		},
  5239  		{
  5240  			Name: "DispatchPayload deleted",
  5241  			Old: &Task{
  5242  				DispatchPayload: &DispatchPayloadConfig{
  5243  					File: "foo",
  5244  				},
  5245  			},
  5246  			New: &Task{},
  5247  			Expected: &TaskDiff{
  5248  				Type: DiffTypeEdited,
  5249  				Objects: []*ObjectDiff{
  5250  					{
  5251  						Type: DiffTypeDeleted,
  5252  						Name: "DispatchPayload",
  5253  						Fields: []*FieldDiff{
  5254  							{
  5255  								Type: DiffTypeDeleted,
  5256  								Name: "File",
  5257  								Old:  "foo",
  5258  								New:  "",
  5259  							},
  5260  						},
  5261  					},
  5262  				},
  5263  			},
  5264  		},
  5265  		{
  5266  			Name: "Dispatch payload edited",
  5267  			Old: &Task{
  5268  				DispatchPayload: &DispatchPayloadConfig{
  5269  					File: "foo",
  5270  				},
  5271  			},
  5272  			New: &Task{
  5273  				DispatchPayload: &DispatchPayloadConfig{
  5274  					File: "bar",
  5275  				},
  5276  			},
  5277  			Expected: &TaskDiff{
  5278  				Type: DiffTypeEdited,
  5279  				Objects: []*ObjectDiff{
  5280  					{
  5281  						Type: DiffTypeEdited,
  5282  						Name: "DispatchPayload",
  5283  						Fields: []*FieldDiff{
  5284  							{
  5285  								Type: DiffTypeEdited,
  5286  								Name: "File",
  5287  								Old:  "foo",
  5288  								New:  "bar",
  5289  							},
  5290  						},
  5291  					},
  5292  				},
  5293  			},
  5294  		},
  5295  		{
  5296  			// Place holder for if more fields are added
  5297  			Name:       "DispatchPayload edited with context",
  5298  			Contextual: true,
  5299  			Old: &Task{
  5300  				DispatchPayload: &DispatchPayloadConfig{
  5301  					File: "foo",
  5302  				},
  5303  			},
  5304  			New: &Task{
  5305  				DispatchPayload: &DispatchPayloadConfig{
  5306  					File: "bar",
  5307  				},
  5308  			},
  5309  			Expected: &TaskDiff{
  5310  				Type: DiffTypeEdited,
  5311  				Objects: []*ObjectDiff{
  5312  					{
  5313  						Type: DiffTypeEdited,
  5314  						Name: "DispatchPayload",
  5315  						Fields: []*FieldDiff{
  5316  							{
  5317  								Type: DiffTypeEdited,
  5318  								Name: "File",
  5319  								Old:  "foo",
  5320  								New:  "bar",
  5321  							},
  5322  						},
  5323  					},
  5324  				},
  5325  			},
  5326  		},
  5327  	}
  5328  
  5329  	for i, c := range cases {
  5330  		t.Run(c.Name, func(t *testing.T) {
  5331  			actual, err := c.Old.Diff(c.New, c.Contextual)
  5332  			if c.Error && err == nil {
  5333  				t.Fatalf("case %d: expected errored", i+1)
  5334  			} else if err != nil {
  5335  				if !c.Error {
  5336  					t.Fatalf("case %d: errored %#v", i+1, err)
  5337  				} else {
  5338  					return
  5339  				}
  5340  			}
  5341  
  5342  			if !reflect.DeepEqual(actual, c.Expected) {
  5343  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  5344  					i+1, actual, c.Expected)
  5345  			}
  5346  		})
  5347  	}
  5348  }