github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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: "AutoRevert",
  1983  								Old:  "true",
  1984  								New:  "",
  1985  							},
  1986  							{
  1987  								Type: DiffTypeDeleted,
  1988  								Name: "Canary",
  1989  								Old:  "0",
  1990  								New:  "",
  1991  							},
  1992  							{
  1993  								Type: DiffTypeDeleted,
  1994  								Name: "HealthyDeadline",
  1995  								Old:  "0",
  1996  								New:  "",
  1997  							},
  1998  							{
  1999  								Type: DiffTypeDeleted,
  2000  								Name: "MaxParallel",
  2001  								Old:  "0",
  2002  								New:  "",
  2003  							},
  2004  							{
  2005  								Type: DiffTypeDeleted,
  2006  								Name: "MinHealthyTime",
  2007  								Old:  "0",
  2008  								New:  "",
  2009  							},
  2010  							{
  2011  								Type: DiffTypeDeleted,
  2012  								Name: "ProgressDeadline",
  2013  								Old:  "0",
  2014  								New:  "",
  2015  							},
  2016  						},
  2017  					},
  2018  				},
  2019  			},
  2020  		},
  2021  		{
  2022  			// Update strategy added
  2023  			Old: &TaskGroup{},
  2024  			New: &TaskGroup{
  2025  				Update: &UpdateStrategy{
  2026  					AutoRevert: true,
  2027  				},
  2028  			},
  2029  			Expected: &TaskGroupDiff{
  2030  				Type: DiffTypeEdited,
  2031  				Objects: []*ObjectDiff{
  2032  					{
  2033  						Type: DiffTypeAdded,
  2034  						Name: "Update",
  2035  						Fields: []*FieldDiff{
  2036  							{
  2037  								Type: DiffTypeAdded,
  2038  								Name: "AutoRevert",
  2039  								Old:  "",
  2040  								New:  "true",
  2041  							},
  2042  							{
  2043  								Type: DiffTypeAdded,
  2044  								Name: "Canary",
  2045  								Old:  "",
  2046  								New:  "0",
  2047  							},
  2048  							{
  2049  								Type: DiffTypeAdded,
  2050  								Name: "HealthyDeadline",
  2051  								Old:  "",
  2052  								New:  "0",
  2053  							},
  2054  							{
  2055  								Type: DiffTypeAdded,
  2056  								Name: "MaxParallel",
  2057  								Old:  "",
  2058  								New:  "0",
  2059  							},
  2060  							{
  2061  								Type: DiffTypeAdded,
  2062  								Name: "MinHealthyTime",
  2063  								Old:  "",
  2064  								New:  "0",
  2065  							},
  2066  							{
  2067  								Type: DiffTypeAdded,
  2068  								Name: "ProgressDeadline",
  2069  								Old:  "",
  2070  								New:  "0",
  2071  							},
  2072  						},
  2073  					},
  2074  				},
  2075  			},
  2076  		},
  2077  		{
  2078  			// Update strategy edited
  2079  			Old: &TaskGroup{
  2080  				Update: &UpdateStrategy{
  2081  					MaxParallel:      5,
  2082  					HealthCheck:      "foo",
  2083  					MinHealthyTime:   1 * time.Second,
  2084  					HealthyDeadline:  30 * time.Second,
  2085  					ProgressDeadline: 29 * time.Second,
  2086  					AutoRevert:       true,
  2087  					Canary:           2,
  2088  				},
  2089  			},
  2090  			New: &TaskGroup{
  2091  				Update: &UpdateStrategy{
  2092  					MaxParallel:      7,
  2093  					HealthCheck:      "bar",
  2094  					MinHealthyTime:   2 * time.Second,
  2095  					HealthyDeadline:  31 * time.Second,
  2096  					ProgressDeadline: 32 * time.Second,
  2097  					AutoRevert:       false,
  2098  					Canary:           1,
  2099  				},
  2100  			},
  2101  			Expected: &TaskGroupDiff{
  2102  				Type: DiffTypeEdited,
  2103  				Objects: []*ObjectDiff{
  2104  					{
  2105  						Type: DiffTypeEdited,
  2106  						Name: "Update",
  2107  						Fields: []*FieldDiff{
  2108  							{
  2109  								Type: DiffTypeEdited,
  2110  								Name: "AutoRevert",
  2111  								Old:  "true",
  2112  								New:  "false",
  2113  							},
  2114  							{
  2115  								Type: DiffTypeEdited,
  2116  								Name: "Canary",
  2117  								Old:  "2",
  2118  								New:  "1",
  2119  							},
  2120  							{
  2121  								Type: DiffTypeEdited,
  2122  								Name: "HealthCheck",
  2123  								Old:  "foo",
  2124  								New:  "bar",
  2125  							},
  2126  							{
  2127  								Type: DiffTypeEdited,
  2128  								Name: "HealthyDeadline",
  2129  								Old:  "30000000000",
  2130  								New:  "31000000000",
  2131  							},
  2132  							{
  2133  								Type: DiffTypeEdited,
  2134  								Name: "MaxParallel",
  2135  								Old:  "5",
  2136  								New:  "7",
  2137  							},
  2138  							{
  2139  								Type: DiffTypeEdited,
  2140  								Name: "MinHealthyTime",
  2141  								Old:  "1000000000",
  2142  								New:  "2000000000",
  2143  							},
  2144  							{
  2145  								Type: DiffTypeEdited,
  2146  								Name: "ProgressDeadline",
  2147  								Old:  "29000000000",
  2148  								New:  "32000000000",
  2149  							},
  2150  						},
  2151  					},
  2152  				},
  2153  			},
  2154  		},
  2155  		{
  2156  			// Update strategy edited with context
  2157  			Contextual: true,
  2158  			Old: &TaskGroup{
  2159  				Update: &UpdateStrategy{
  2160  					MaxParallel:      5,
  2161  					HealthCheck:      "foo",
  2162  					MinHealthyTime:   1 * time.Second,
  2163  					HealthyDeadline:  30 * time.Second,
  2164  					ProgressDeadline: 30 * time.Second,
  2165  					AutoRevert:       true,
  2166  					Canary:           2,
  2167  				},
  2168  			},
  2169  			New: &TaskGroup{
  2170  				Update: &UpdateStrategy{
  2171  					MaxParallel:      7,
  2172  					HealthCheck:      "foo",
  2173  					MinHealthyTime:   1 * time.Second,
  2174  					HealthyDeadline:  30 * time.Second,
  2175  					ProgressDeadline: 30 * time.Second,
  2176  					AutoRevert:       true,
  2177  					Canary:           2,
  2178  				},
  2179  			},
  2180  			Expected: &TaskGroupDiff{
  2181  				Type: DiffTypeEdited,
  2182  				Objects: []*ObjectDiff{
  2183  					{
  2184  						Type: DiffTypeEdited,
  2185  						Name: "Update",
  2186  						Fields: []*FieldDiff{
  2187  							{
  2188  								Type: DiffTypeNone,
  2189  								Name: "AutoRevert",
  2190  								Old:  "true",
  2191  								New:  "true",
  2192  							},
  2193  							{
  2194  								Type: DiffTypeNone,
  2195  								Name: "Canary",
  2196  								Old:  "2",
  2197  								New:  "2",
  2198  							},
  2199  							{
  2200  								Type: DiffTypeNone,
  2201  								Name: "HealthCheck",
  2202  								Old:  "foo",
  2203  								New:  "foo",
  2204  							},
  2205  							{
  2206  								Type: DiffTypeNone,
  2207  								Name: "HealthyDeadline",
  2208  								Old:  "30000000000",
  2209  								New:  "30000000000",
  2210  							},
  2211  							{
  2212  								Type: DiffTypeEdited,
  2213  								Name: "MaxParallel",
  2214  								Old:  "5",
  2215  								New:  "7",
  2216  							},
  2217  							{
  2218  								Type: DiffTypeNone,
  2219  								Name: "MinHealthyTime",
  2220  								Old:  "1000000000",
  2221  								New:  "1000000000",
  2222  							},
  2223  							{
  2224  								Type: DiffTypeNone,
  2225  								Name: "ProgressDeadline",
  2226  								Old:  "30000000000",
  2227  								New:  "30000000000",
  2228  							},
  2229  						},
  2230  					},
  2231  				},
  2232  			},
  2233  		},
  2234  		{
  2235  			// EphemeralDisk added
  2236  			Old: &TaskGroup{},
  2237  			New: &TaskGroup{
  2238  				EphemeralDisk: &EphemeralDisk{
  2239  					Migrate: true,
  2240  					Sticky:  true,
  2241  					SizeMB:  100,
  2242  				},
  2243  			},
  2244  			Expected: &TaskGroupDiff{
  2245  				Type: DiffTypeEdited,
  2246  				Objects: []*ObjectDiff{
  2247  					{
  2248  						Type: DiffTypeAdded,
  2249  						Name: "EphemeralDisk",
  2250  						Fields: []*FieldDiff{
  2251  							{
  2252  								Type: DiffTypeAdded,
  2253  								Name: "Migrate",
  2254  								Old:  "",
  2255  								New:  "true",
  2256  							},
  2257  							{
  2258  								Type: DiffTypeAdded,
  2259  								Name: "SizeMB",
  2260  								Old:  "",
  2261  								New:  "100",
  2262  							},
  2263  							{
  2264  								Type: DiffTypeAdded,
  2265  								Name: "Sticky",
  2266  								Old:  "",
  2267  								New:  "true",
  2268  							},
  2269  						},
  2270  					},
  2271  				},
  2272  			},
  2273  		},
  2274  		{
  2275  			// EphemeralDisk deleted
  2276  			Old: &TaskGroup{
  2277  				EphemeralDisk: &EphemeralDisk{
  2278  					Migrate: true,
  2279  					Sticky:  true,
  2280  					SizeMB:  100,
  2281  				},
  2282  			},
  2283  			New: &TaskGroup{},
  2284  			Expected: &TaskGroupDiff{
  2285  				Type: DiffTypeEdited,
  2286  				Objects: []*ObjectDiff{
  2287  					{
  2288  						Type: DiffTypeDeleted,
  2289  						Name: "EphemeralDisk",
  2290  						Fields: []*FieldDiff{
  2291  							{
  2292  								Type: DiffTypeDeleted,
  2293  								Name: "Migrate",
  2294  								Old:  "true",
  2295  								New:  "",
  2296  							},
  2297  							{
  2298  								Type: DiffTypeDeleted,
  2299  								Name: "SizeMB",
  2300  								Old:  "100",
  2301  								New:  "",
  2302  							},
  2303  							{
  2304  								Type: DiffTypeDeleted,
  2305  								Name: "Sticky",
  2306  								Old:  "true",
  2307  								New:  "",
  2308  							},
  2309  						},
  2310  					},
  2311  				},
  2312  			},
  2313  		},
  2314  		{
  2315  			// EphemeralDisk edited
  2316  			Old: &TaskGroup{
  2317  				EphemeralDisk: &EphemeralDisk{
  2318  					Migrate: true,
  2319  					Sticky:  true,
  2320  					SizeMB:  150,
  2321  				},
  2322  			},
  2323  			New: &TaskGroup{
  2324  				EphemeralDisk: &EphemeralDisk{
  2325  					Migrate: false,
  2326  					Sticky:  false,
  2327  					SizeMB:  90,
  2328  				},
  2329  			},
  2330  			Expected: &TaskGroupDiff{
  2331  				Type: DiffTypeEdited,
  2332  				Objects: []*ObjectDiff{
  2333  					{
  2334  						Type: DiffTypeEdited,
  2335  						Name: "EphemeralDisk",
  2336  						Fields: []*FieldDiff{
  2337  							{
  2338  								Type: DiffTypeEdited,
  2339  								Name: "Migrate",
  2340  								Old:  "true",
  2341  								New:  "false",
  2342  							},
  2343  							{
  2344  								Type: DiffTypeEdited,
  2345  								Name: "SizeMB",
  2346  								Old:  "150",
  2347  								New:  "90",
  2348  							},
  2349  
  2350  							{
  2351  								Type: DiffTypeEdited,
  2352  								Name: "Sticky",
  2353  								Old:  "true",
  2354  								New:  "false",
  2355  							},
  2356  						},
  2357  					},
  2358  				},
  2359  			},
  2360  		},
  2361  		{
  2362  			// EphemeralDisk edited with context
  2363  			Contextual: true,
  2364  			Old: &TaskGroup{
  2365  				EphemeralDisk: &EphemeralDisk{
  2366  					Migrate: false,
  2367  					Sticky:  false,
  2368  					SizeMB:  100,
  2369  				},
  2370  			},
  2371  			New: &TaskGroup{
  2372  				EphemeralDisk: &EphemeralDisk{
  2373  					Migrate: true,
  2374  					Sticky:  true,
  2375  					SizeMB:  90,
  2376  				},
  2377  			},
  2378  			Expected: &TaskGroupDiff{
  2379  				Type: DiffTypeEdited,
  2380  				Objects: []*ObjectDiff{
  2381  					{
  2382  						Type: DiffTypeEdited,
  2383  						Name: "EphemeralDisk",
  2384  						Fields: []*FieldDiff{
  2385  							{
  2386  								Type: DiffTypeEdited,
  2387  								Name: "Migrate",
  2388  								Old:  "false",
  2389  								New:  "true",
  2390  							},
  2391  							{
  2392  								Type: DiffTypeEdited,
  2393  								Name: "SizeMB",
  2394  								Old:  "100",
  2395  								New:  "90",
  2396  							},
  2397  							{
  2398  								Type: DiffTypeEdited,
  2399  								Name: "Sticky",
  2400  								Old:  "false",
  2401  								New:  "true",
  2402  							},
  2403  						},
  2404  					},
  2405  				},
  2406  			},
  2407  		},
  2408  		{
  2409  			// Tasks edited
  2410  			Old: &TaskGroup{
  2411  				Tasks: []*Task{
  2412  					{
  2413  						Name:   "foo",
  2414  						Driver: "docker",
  2415  					},
  2416  					{
  2417  						Name:   "bar",
  2418  						Driver: "docker",
  2419  					},
  2420  					{
  2421  						Name:          "baz",
  2422  						ShutdownDelay: 1 * time.Second,
  2423  					},
  2424  				},
  2425  			},
  2426  			New: &TaskGroup{
  2427  				Tasks: []*Task{
  2428  					{
  2429  						Name:   "bar",
  2430  						Driver: "docker",
  2431  					},
  2432  					{
  2433  						Name:   "bam",
  2434  						Driver: "docker",
  2435  					},
  2436  					{
  2437  						Name:          "baz",
  2438  						ShutdownDelay: 2 * time.Second,
  2439  					},
  2440  				},
  2441  			},
  2442  			Expected: &TaskGroupDiff{
  2443  				Type: DiffTypeEdited,
  2444  				Tasks: []*TaskDiff{
  2445  					{
  2446  						Type: DiffTypeAdded,
  2447  						Name: "bam",
  2448  						Fields: []*FieldDiff{
  2449  							{
  2450  								Type: DiffTypeAdded,
  2451  								Name: "Driver",
  2452  								Old:  "",
  2453  								New:  "docker",
  2454  							},
  2455  							{
  2456  								Type: DiffTypeAdded,
  2457  								Name: "KillTimeout",
  2458  								Old:  "",
  2459  								New:  "0",
  2460  							},
  2461  							{
  2462  								Type: DiffTypeAdded,
  2463  								Name: "Leader",
  2464  								Old:  "",
  2465  								New:  "false",
  2466  							},
  2467  							{
  2468  								Type: DiffTypeAdded,
  2469  								Name: "ShutdownDelay",
  2470  								Old:  "",
  2471  								New:  "0",
  2472  							},
  2473  						},
  2474  					},
  2475  					{
  2476  						Type: DiffTypeNone,
  2477  						Name: "bar",
  2478  					},
  2479  					{
  2480  						Type: DiffTypeEdited,
  2481  						Name: "baz",
  2482  						Fields: []*FieldDiff{
  2483  							{
  2484  								Type: DiffTypeEdited,
  2485  								Name: "ShutdownDelay",
  2486  								Old:  "1000000000",
  2487  								New:  "2000000000",
  2488  							},
  2489  						},
  2490  					},
  2491  					{
  2492  						Type: DiffTypeDeleted,
  2493  						Name: "foo",
  2494  						Fields: []*FieldDiff{
  2495  							{
  2496  								Type: DiffTypeDeleted,
  2497  								Name: "Driver",
  2498  								Old:  "docker",
  2499  								New:  "",
  2500  							},
  2501  							{
  2502  								Type: DiffTypeDeleted,
  2503  								Name: "KillTimeout",
  2504  								Old:  "0",
  2505  								New:  "",
  2506  							},
  2507  							{
  2508  								Type: DiffTypeDeleted,
  2509  								Name: "Leader",
  2510  								Old:  "false",
  2511  								New:  "",
  2512  							},
  2513  							{
  2514  								Type: DiffTypeDeleted,
  2515  								Name: "ShutdownDelay",
  2516  								Old:  "0",
  2517  								New:  "",
  2518  							},
  2519  						},
  2520  					},
  2521  				},
  2522  			},
  2523  		},
  2524  	}
  2525  
  2526  	for i, c := range cases {
  2527  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2528  		if c.Error && err == nil {
  2529  			t.Fatalf("case %d: expected errored", i+1)
  2530  		} else if err != nil {
  2531  			if !c.Error {
  2532  				t.Fatalf("case %d: errored %#v", i+1, err)
  2533  			} else {
  2534  				continue
  2535  			}
  2536  		}
  2537  
  2538  		if !reflect.DeepEqual(actual, c.Expected) {
  2539  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2540  				i+1, actual, c.Expected)
  2541  		}
  2542  	}
  2543  }
  2544  
  2545  func TestTaskDiff(t *testing.T) {
  2546  	cases := []struct {
  2547  		Name       string
  2548  		Old, New   *Task
  2549  		Expected   *TaskDiff
  2550  		Error      bool
  2551  		Contextual bool
  2552  	}{
  2553  		{
  2554  			Name: "Empty",
  2555  			Old:  nil,
  2556  			New:  nil,
  2557  			Expected: &TaskDiff{
  2558  				Type: DiffTypeNone,
  2559  			},
  2560  		},
  2561  		{
  2562  			Name: "Primitive only that has different names",
  2563  			Old: &Task{
  2564  				Name: "foo",
  2565  				Meta: map[string]string{
  2566  					"foo": "bar",
  2567  				},
  2568  			},
  2569  			New: &Task{
  2570  				Name: "bar",
  2571  				Meta: map[string]string{
  2572  					"foo": "bar",
  2573  				},
  2574  			},
  2575  			Error: true,
  2576  		},
  2577  		{
  2578  			Name: "Primitive only that is the same",
  2579  			Old: &Task{
  2580  				Name:   "foo",
  2581  				Driver: "exec",
  2582  				User:   "foo",
  2583  				Env: map[string]string{
  2584  					"FOO": "bar",
  2585  				},
  2586  				Meta: map[string]string{
  2587  					"foo": "bar",
  2588  				},
  2589  				KillTimeout: 1 * time.Second,
  2590  				Leader:      true,
  2591  			},
  2592  			New: &Task{
  2593  				Name:   "foo",
  2594  				Driver: "exec",
  2595  				User:   "foo",
  2596  				Env: map[string]string{
  2597  					"FOO": "bar",
  2598  				},
  2599  				Meta: map[string]string{
  2600  					"foo": "bar",
  2601  				},
  2602  				KillTimeout: 1 * time.Second,
  2603  				Leader:      true,
  2604  			},
  2605  			Expected: &TaskDiff{
  2606  				Type: DiffTypeNone,
  2607  				Name: "foo",
  2608  			},
  2609  		},
  2610  		{
  2611  			Name: "Primitive only that has diffs",
  2612  			Old: &Task{
  2613  				Name:   "foo",
  2614  				Driver: "exec",
  2615  				User:   "foo",
  2616  				Env: map[string]string{
  2617  					"FOO": "bar",
  2618  				},
  2619  				Meta: map[string]string{
  2620  					"foo": "bar",
  2621  				},
  2622  				KillTimeout: 1 * time.Second,
  2623  				Leader:      true,
  2624  			},
  2625  			New: &Task{
  2626  				Name:   "foo",
  2627  				Driver: "docker",
  2628  				User:   "bar",
  2629  				Env: map[string]string{
  2630  					"FOO": "baz",
  2631  				},
  2632  				Meta: map[string]string{
  2633  					"foo": "baz",
  2634  				},
  2635  				KillTimeout: 2 * time.Second,
  2636  				Leader:      false,
  2637  			},
  2638  			Expected: &TaskDiff{
  2639  				Type: DiffTypeEdited,
  2640  				Name: "foo",
  2641  				Fields: []*FieldDiff{
  2642  					{
  2643  						Type: DiffTypeEdited,
  2644  						Name: "Driver",
  2645  						Old:  "exec",
  2646  						New:  "docker",
  2647  					},
  2648  					{
  2649  						Type: DiffTypeEdited,
  2650  						Name: "Env[FOO]",
  2651  						Old:  "bar",
  2652  						New:  "baz",
  2653  					},
  2654  					{
  2655  						Type: DiffTypeEdited,
  2656  						Name: "KillTimeout",
  2657  						Old:  "1000000000",
  2658  						New:  "2000000000",
  2659  					},
  2660  					{
  2661  						Type: DiffTypeEdited,
  2662  						Name: "Leader",
  2663  						Old:  "true",
  2664  						New:  "false",
  2665  					},
  2666  					{
  2667  						Type: DiffTypeEdited,
  2668  						Name: "Meta[foo]",
  2669  						Old:  "bar",
  2670  						New:  "baz",
  2671  					},
  2672  					{
  2673  						Type: DiffTypeEdited,
  2674  						Name: "User",
  2675  						Old:  "foo",
  2676  						New:  "bar",
  2677  					},
  2678  				},
  2679  			},
  2680  		},
  2681  		{
  2682  			Name: "Map diff",
  2683  			Old: &Task{
  2684  				Meta: map[string]string{
  2685  					"foo": "foo",
  2686  					"bar": "bar",
  2687  				},
  2688  				Env: map[string]string{
  2689  					"foo": "foo",
  2690  					"bar": "bar",
  2691  				},
  2692  			},
  2693  			New: &Task{
  2694  				Meta: map[string]string{
  2695  					"bar": "bar",
  2696  					"baz": "baz",
  2697  				},
  2698  				Env: map[string]string{
  2699  					"bar": "bar",
  2700  					"baz": "baz",
  2701  				},
  2702  			},
  2703  			Expected: &TaskDiff{
  2704  				Type: DiffTypeEdited,
  2705  				Fields: []*FieldDiff{
  2706  					{
  2707  						Type: DiffTypeAdded,
  2708  						Name: "Env[baz]",
  2709  						Old:  "",
  2710  						New:  "baz",
  2711  					},
  2712  					{
  2713  						Type: DiffTypeDeleted,
  2714  						Name: "Env[foo]",
  2715  						Old:  "foo",
  2716  						New:  "",
  2717  					},
  2718  					{
  2719  						Type: DiffTypeAdded,
  2720  						Name: "Meta[baz]",
  2721  						Old:  "",
  2722  						New:  "baz",
  2723  					},
  2724  					{
  2725  						Type: DiffTypeDeleted,
  2726  						Name: "Meta[foo]",
  2727  						Old:  "foo",
  2728  						New:  "",
  2729  					},
  2730  				},
  2731  			},
  2732  		},
  2733  		{
  2734  			Name: "Constraints edited",
  2735  			Old: &Task{
  2736  				Constraints: []*Constraint{
  2737  					{
  2738  						LTarget: "foo",
  2739  						RTarget: "foo",
  2740  						Operand: "foo",
  2741  						str:     "foo",
  2742  					},
  2743  					{
  2744  						LTarget: "bar",
  2745  						RTarget: "bar",
  2746  						Operand: "bar",
  2747  						str:     "bar",
  2748  					},
  2749  				},
  2750  			},
  2751  			New: &Task{
  2752  				Constraints: []*Constraint{
  2753  					{
  2754  						LTarget: "foo",
  2755  						RTarget: "foo",
  2756  						Operand: "foo",
  2757  						str:     "foo",
  2758  					},
  2759  					{
  2760  						LTarget: "baz",
  2761  						RTarget: "baz",
  2762  						Operand: "baz",
  2763  						str:     "baz",
  2764  					},
  2765  				},
  2766  			},
  2767  			Expected: &TaskDiff{
  2768  				Type: DiffTypeEdited,
  2769  				Objects: []*ObjectDiff{
  2770  					{
  2771  						Type: DiffTypeAdded,
  2772  						Name: "Constraint",
  2773  						Fields: []*FieldDiff{
  2774  							{
  2775  								Type: DiffTypeAdded,
  2776  								Name: "LTarget",
  2777  								Old:  "",
  2778  								New:  "baz",
  2779  							},
  2780  							{
  2781  								Type: DiffTypeAdded,
  2782  								Name: "Operand",
  2783  								Old:  "",
  2784  								New:  "baz",
  2785  							},
  2786  							{
  2787  								Type: DiffTypeAdded,
  2788  								Name: "RTarget",
  2789  								Old:  "",
  2790  								New:  "baz",
  2791  							},
  2792  						},
  2793  					},
  2794  					{
  2795  						Type: DiffTypeDeleted,
  2796  						Name: "Constraint",
  2797  						Fields: []*FieldDiff{
  2798  							{
  2799  								Type: DiffTypeDeleted,
  2800  								Name: "LTarget",
  2801  								Old:  "bar",
  2802  								New:  "",
  2803  							},
  2804  							{
  2805  								Type: DiffTypeDeleted,
  2806  								Name: "Operand",
  2807  								Old:  "bar",
  2808  								New:  "",
  2809  							},
  2810  							{
  2811  								Type: DiffTypeDeleted,
  2812  								Name: "RTarget",
  2813  								Old:  "bar",
  2814  								New:  "",
  2815  							},
  2816  						},
  2817  					},
  2818  				},
  2819  			},
  2820  		},
  2821  		{
  2822  			Name: "Affinities edited",
  2823  			Old: &Task{
  2824  				Affinities: []*Affinity{
  2825  					{
  2826  						LTarget: "foo",
  2827  						RTarget: "foo",
  2828  						Operand: "foo",
  2829  						Weight:  20,
  2830  						str:     "foo",
  2831  					},
  2832  					{
  2833  						LTarget: "bar",
  2834  						RTarget: "bar",
  2835  						Operand: "bar",
  2836  						Weight:  20,
  2837  						str:     "bar",
  2838  					},
  2839  				},
  2840  			},
  2841  			New: &Task{
  2842  				Affinities: []*Affinity{
  2843  					{
  2844  						LTarget: "foo",
  2845  						RTarget: "foo",
  2846  						Operand: "foo",
  2847  						Weight:  20,
  2848  						str:     "foo",
  2849  					},
  2850  					{
  2851  						LTarget: "baz",
  2852  						RTarget: "baz",
  2853  						Operand: "baz",
  2854  						Weight:  20,
  2855  						str:     "baz",
  2856  					},
  2857  				},
  2858  			},
  2859  			Expected: &TaskDiff{
  2860  				Type: DiffTypeEdited,
  2861  				Objects: []*ObjectDiff{
  2862  					{
  2863  						Type: DiffTypeAdded,
  2864  						Name: "Affinity",
  2865  						Fields: []*FieldDiff{
  2866  							{
  2867  								Type: DiffTypeAdded,
  2868  								Name: "LTarget",
  2869  								Old:  "",
  2870  								New:  "baz",
  2871  							},
  2872  							{
  2873  								Type: DiffTypeAdded,
  2874  								Name: "Operand",
  2875  								Old:  "",
  2876  								New:  "baz",
  2877  							},
  2878  							{
  2879  								Type: DiffTypeAdded,
  2880  								Name: "RTarget",
  2881  								Old:  "",
  2882  								New:  "baz",
  2883  							},
  2884  							{
  2885  								Type: DiffTypeAdded,
  2886  								Name: "Weight",
  2887  								Old:  "",
  2888  								New:  "20",
  2889  							},
  2890  						},
  2891  					},
  2892  					{
  2893  						Type: DiffTypeDeleted,
  2894  						Name: "Affinity",
  2895  						Fields: []*FieldDiff{
  2896  							{
  2897  								Type: DiffTypeDeleted,
  2898  								Name: "LTarget",
  2899  								Old:  "bar",
  2900  								New:  "",
  2901  							},
  2902  							{
  2903  								Type: DiffTypeDeleted,
  2904  								Name: "Operand",
  2905  								Old:  "bar",
  2906  								New:  "",
  2907  							},
  2908  							{
  2909  								Type: DiffTypeDeleted,
  2910  								Name: "RTarget",
  2911  								Old:  "bar",
  2912  								New:  "",
  2913  							},
  2914  							{
  2915  								Type: DiffTypeDeleted,
  2916  								Name: "Weight",
  2917  								Old:  "20",
  2918  								New:  "",
  2919  							},
  2920  						},
  2921  					},
  2922  				},
  2923  			},
  2924  		},
  2925  		{
  2926  			Name: "LogConfig added",
  2927  			Old:  &Task{},
  2928  			New: &Task{
  2929  				LogConfig: &LogConfig{
  2930  					MaxFiles:      1,
  2931  					MaxFileSizeMB: 10,
  2932  				},
  2933  			},
  2934  			Expected: &TaskDiff{
  2935  				Type: DiffTypeEdited,
  2936  				Objects: []*ObjectDiff{
  2937  					{
  2938  						Type: DiffTypeAdded,
  2939  						Name: "LogConfig",
  2940  						Fields: []*FieldDiff{
  2941  							{
  2942  								Type: DiffTypeAdded,
  2943  								Name: "MaxFileSizeMB",
  2944  								Old:  "",
  2945  								New:  "10",
  2946  							},
  2947  							{
  2948  								Type: DiffTypeAdded,
  2949  								Name: "MaxFiles",
  2950  								Old:  "",
  2951  								New:  "1",
  2952  							},
  2953  						},
  2954  					},
  2955  				},
  2956  			},
  2957  		},
  2958  		{
  2959  			Name: "LogConfig deleted",
  2960  			Old: &Task{
  2961  				LogConfig: &LogConfig{
  2962  					MaxFiles:      1,
  2963  					MaxFileSizeMB: 10,
  2964  				},
  2965  			},
  2966  			New: &Task{},
  2967  			Expected: &TaskDiff{
  2968  				Type: DiffTypeEdited,
  2969  				Objects: []*ObjectDiff{
  2970  					{
  2971  						Type: DiffTypeDeleted,
  2972  						Name: "LogConfig",
  2973  						Fields: []*FieldDiff{
  2974  							{
  2975  								Type: DiffTypeDeleted,
  2976  								Name: "MaxFileSizeMB",
  2977  								Old:  "10",
  2978  								New:  "",
  2979  							},
  2980  							{
  2981  								Type: DiffTypeDeleted,
  2982  								Name: "MaxFiles",
  2983  								Old:  "1",
  2984  								New:  "",
  2985  							},
  2986  						},
  2987  					},
  2988  				},
  2989  			},
  2990  		},
  2991  		{
  2992  			Name: "LogConfig edited",
  2993  			Old: &Task{
  2994  				LogConfig: &LogConfig{
  2995  					MaxFiles:      1,
  2996  					MaxFileSizeMB: 10,
  2997  				},
  2998  			},
  2999  			New: &Task{
  3000  				LogConfig: &LogConfig{
  3001  					MaxFiles:      2,
  3002  					MaxFileSizeMB: 20,
  3003  				},
  3004  			},
  3005  			Expected: &TaskDiff{
  3006  				Type: DiffTypeEdited,
  3007  				Objects: []*ObjectDiff{
  3008  					{
  3009  						Type: DiffTypeEdited,
  3010  						Name: "LogConfig",
  3011  						Fields: []*FieldDiff{
  3012  							{
  3013  								Type: DiffTypeEdited,
  3014  								Name: "MaxFileSizeMB",
  3015  								Old:  "10",
  3016  								New:  "20",
  3017  							},
  3018  							{
  3019  								Type: DiffTypeEdited,
  3020  								Name: "MaxFiles",
  3021  								Old:  "1",
  3022  								New:  "2",
  3023  							},
  3024  						},
  3025  					},
  3026  				},
  3027  			},
  3028  		},
  3029  		{
  3030  			Name:       "LogConfig edited with context",
  3031  			Contextual: true,
  3032  			Old: &Task{
  3033  				LogConfig: &LogConfig{
  3034  					MaxFiles:      1,
  3035  					MaxFileSizeMB: 10,
  3036  				},
  3037  			},
  3038  			New: &Task{
  3039  				LogConfig: &LogConfig{
  3040  					MaxFiles:      1,
  3041  					MaxFileSizeMB: 20,
  3042  				},
  3043  			},
  3044  			Expected: &TaskDiff{
  3045  				Type: DiffTypeEdited,
  3046  				Objects: []*ObjectDiff{
  3047  					{
  3048  						Type: DiffTypeEdited,
  3049  						Name: "LogConfig",
  3050  						Fields: []*FieldDiff{
  3051  							{
  3052  								Type: DiffTypeEdited,
  3053  								Name: "MaxFileSizeMB",
  3054  								Old:  "10",
  3055  								New:  "20",
  3056  							},
  3057  							{
  3058  								Type: DiffTypeNone,
  3059  								Name: "MaxFiles",
  3060  								Old:  "1",
  3061  								New:  "1",
  3062  							},
  3063  						},
  3064  					},
  3065  				},
  3066  			},
  3067  		},
  3068  		{
  3069  			Name: "Artifacts edited",
  3070  			Old: &Task{
  3071  				Artifacts: []*TaskArtifact{
  3072  					{
  3073  						GetterSource: "foo",
  3074  						GetterOptions: map[string]string{
  3075  							"foo": "bar",
  3076  						},
  3077  						RelativeDest: "foo",
  3078  					},
  3079  					{
  3080  						GetterSource: "bar",
  3081  						GetterOptions: map[string]string{
  3082  							"bar": "baz",
  3083  						},
  3084  						GetterMode:   "dir",
  3085  						RelativeDest: "bar",
  3086  					},
  3087  				},
  3088  			},
  3089  			New: &Task{
  3090  				Artifacts: []*TaskArtifact{
  3091  					{
  3092  						GetterSource: "foo",
  3093  						GetterOptions: map[string]string{
  3094  							"foo": "bar",
  3095  						},
  3096  						RelativeDest: "foo",
  3097  					},
  3098  					{
  3099  						GetterSource: "bam",
  3100  						GetterOptions: map[string]string{
  3101  							"bam": "baz",
  3102  						},
  3103  						GetterMode:   "file",
  3104  						RelativeDest: "bam",
  3105  					},
  3106  				},
  3107  			},
  3108  			Expected: &TaskDiff{
  3109  				Type: DiffTypeEdited,
  3110  				Objects: []*ObjectDiff{
  3111  					{
  3112  						Type: DiffTypeAdded,
  3113  						Name: "Artifact",
  3114  						Fields: []*FieldDiff{
  3115  							{
  3116  								Type: DiffTypeAdded,
  3117  								Name: "GetterMode",
  3118  								Old:  "",
  3119  								New:  "file",
  3120  							},
  3121  							{
  3122  								Type: DiffTypeAdded,
  3123  								Name: "GetterOptions[bam]",
  3124  								Old:  "",
  3125  								New:  "baz",
  3126  							},
  3127  							{
  3128  								Type: DiffTypeAdded,
  3129  								Name: "GetterSource",
  3130  								Old:  "",
  3131  								New:  "bam",
  3132  							},
  3133  							{
  3134  								Type: DiffTypeAdded,
  3135  								Name: "RelativeDest",
  3136  								Old:  "",
  3137  								New:  "bam",
  3138  							},
  3139  						},
  3140  					},
  3141  					{
  3142  						Type: DiffTypeDeleted,
  3143  						Name: "Artifact",
  3144  						Fields: []*FieldDiff{
  3145  							{
  3146  								Type: DiffTypeDeleted,
  3147  								Name: "GetterMode",
  3148  								Old:  "dir",
  3149  								New:  "",
  3150  							},
  3151  							{
  3152  								Type: DiffTypeDeleted,
  3153  								Name: "GetterOptions[bar]",
  3154  								Old:  "baz",
  3155  								New:  "",
  3156  							},
  3157  							{
  3158  								Type: DiffTypeDeleted,
  3159  								Name: "GetterSource",
  3160  								Old:  "bar",
  3161  								New:  "",
  3162  							},
  3163  							{
  3164  								Type: DiffTypeDeleted,
  3165  								Name: "RelativeDest",
  3166  								Old:  "bar",
  3167  								New:  "",
  3168  							},
  3169  						},
  3170  					},
  3171  				},
  3172  			},
  3173  		},
  3174  		{
  3175  			Name: "Resources edited (no networks)",
  3176  			Old: &Task{
  3177  				Resources: &Resources{
  3178  					CPU:      100,
  3179  					MemoryMB: 100,
  3180  					DiskMB:   100,
  3181  					IOPS:     100,
  3182  				},
  3183  			},
  3184  			New: &Task{
  3185  				Resources: &Resources{
  3186  					CPU:      200,
  3187  					MemoryMB: 200,
  3188  					DiskMB:   200,
  3189  					IOPS:     200,
  3190  				},
  3191  			},
  3192  			Expected: &TaskDiff{
  3193  				Type: DiffTypeEdited,
  3194  				Objects: []*ObjectDiff{
  3195  					{
  3196  						Type: DiffTypeEdited,
  3197  						Name: "Resources",
  3198  						Fields: []*FieldDiff{
  3199  							{
  3200  								Type: DiffTypeEdited,
  3201  								Name: "CPU",
  3202  								Old:  "100",
  3203  								New:  "200",
  3204  							},
  3205  							{
  3206  								Type: DiffTypeEdited,
  3207  								Name: "DiskMB",
  3208  								Old:  "100",
  3209  								New:  "200",
  3210  							},
  3211  							{
  3212  								Type: DiffTypeEdited,
  3213  								Name: "IOPS",
  3214  								Old:  "100",
  3215  								New:  "200",
  3216  							},
  3217  							{
  3218  								Type: DiffTypeEdited,
  3219  								Name: "MemoryMB",
  3220  								Old:  "100",
  3221  								New:  "200",
  3222  							},
  3223  						},
  3224  					},
  3225  				},
  3226  			},
  3227  		},
  3228  		{
  3229  			Name:       "Resources edited (no networks) with context",
  3230  			Contextual: true,
  3231  			Old: &Task{
  3232  				Resources: &Resources{
  3233  					CPU:      100,
  3234  					MemoryMB: 100,
  3235  					DiskMB:   100,
  3236  					IOPS:     100,
  3237  				},
  3238  			},
  3239  			New: &Task{
  3240  				Resources: &Resources{
  3241  					CPU:      200,
  3242  					MemoryMB: 100,
  3243  					DiskMB:   200,
  3244  					IOPS:     100,
  3245  				},
  3246  			},
  3247  			Expected: &TaskDiff{
  3248  				Type: DiffTypeEdited,
  3249  				Objects: []*ObjectDiff{
  3250  					{
  3251  						Type: DiffTypeEdited,
  3252  						Name: "Resources",
  3253  						Fields: []*FieldDiff{
  3254  							{
  3255  								Type: DiffTypeEdited,
  3256  								Name: "CPU",
  3257  								Old:  "100",
  3258  								New:  "200",
  3259  							},
  3260  							{
  3261  								Type: DiffTypeEdited,
  3262  								Name: "DiskMB",
  3263  								Old:  "100",
  3264  								New:  "200",
  3265  							},
  3266  							{
  3267  								Type: DiffTypeNone,
  3268  								Name: "IOPS",
  3269  								Old:  "100",
  3270  								New:  "100",
  3271  							},
  3272  							{
  3273  								Type: DiffTypeNone,
  3274  								Name: "MemoryMB",
  3275  								Old:  "100",
  3276  								New:  "100",
  3277  							},
  3278  						},
  3279  					},
  3280  				},
  3281  			},
  3282  		},
  3283  		{
  3284  			Name: "Network Resources edited",
  3285  			Old: &Task{
  3286  				Resources: &Resources{
  3287  					Networks: []*NetworkResource{
  3288  						{
  3289  							Device: "foo",
  3290  							CIDR:   "foo",
  3291  							IP:     "foo",
  3292  							MBits:  100,
  3293  							ReservedPorts: []Port{
  3294  								{
  3295  									Label: "foo",
  3296  									Value: 80,
  3297  								},
  3298  							},
  3299  							DynamicPorts: []Port{
  3300  								{
  3301  									Label: "bar",
  3302  								},
  3303  							},
  3304  						},
  3305  					},
  3306  				},
  3307  			},
  3308  			New: &Task{
  3309  				Resources: &Resources{
  3310  					Networks: []*NetworkResource{
  3311  						{
  3312  							Device: "bar",
  3313  							CIDR:   "bar",
  3314  							IP:     "bar",
  3315  							MBits:  200,
  3316  							ReservedPorts: []Port{
  3317  								{
  3318  									Label: "foo",
  3319  									Value: 81,
  3320  								},
  3321  							},
  3322  							DynamicPorts: []Port{
  3323  								{
  3324  									Label: "baz",
  3325  								},
  3326  							},
  3327  						},
  3328  					},
  3329  				},
  3330  			},
  3331  			Expected: &TaskDiff{
  3332  				Type: DiffTypeEdited,
  3333  				Objects: []*ObjectDiff{
  3334  					{
  3335  						Type: DiffTypeEdited,
  3336  						Name: "Resources",
  3337  						Objects: []*ObjectDiff{
  3338  							{
  3339  								Type: DiffTypeAdded,
  3340  								Name: "Network",
  3341  								Fields: []*FieldDiff{
  3342  									{
  3343  										Type: DiffTypeAdded,
  3344  										Name: "MBits",
  3345  										Old:  "",
  3346  										New:  "200",
  3347  									},
  3348  								},
  3349  								Objects: []*ObjectDiff{
  3350  									{
  3351  										Type: DiffTypeAdded,
  3352  										Name: "Static Port",
  3353  										Fields: []*FieldDiff{
  3354  											{
  3355  												Type: DiffTypeAdded,
  3356  												Name: "Label",
  3357  												Old:  "",
  3358  												New:  "foo",
  3359  											},
  3360  											{
  3361  												Type: DiffTypeAdded,
  3362  												Name: "Value",
  3363  												Old:  "",
  3364  												New:  "81",
  3365  											},
  3366  										},
  3367  									},
  3368  									{
  3369  										Type: DiffTypeAdded,
  3370  										Name: "Dynamic Port",
  3371  										Fields: []*FieldDiff{
  3372  											{
  3373  												Type: DiffTypeAdded,
  3374  												Name: "Label",
  3375  												Old:  "",
  3376  												New:  "baz",
  3377  											},
  3378  										},
  3379  									},
  3380  								},
  3381  							},
  3382  							{
  3383  								Type: DiffTypeDeleted,
  3384  								Name: "Network",
  3385  								Fields: []*FieldDiff{
  3386  									{
  3387  										Type: DiffTypeDeleted,
  3388  										Name: "MBits",
  3389  										Old:  "100",
  3390  										New:  "",
  3391  									},
  3392  								},
  3393  								Objects: []*ObjectDiff{
  3394  									{
  3395  										Type: DiffTypeDeleted,
  3396  										Name: "Static Port",
  3397  										Fields: []*FieldDiff{
  3398  											{
  3399  												Type: DiffTypeDeleted,
  3400  												Name: "Label",
  3401  												Old:  "foo",
  3402  												New:  "",
  3403  											},
  3404  											{
  3405  												Type: DiffTypeDeleted,
  3406  												Name: "Value",
  3407  												Old:  "80",
  3408  												New:  "",
  3409  											},
  3410  										},
  3411  									},
  3412  									{
  3413  										Type: DiffTypeDeleted,
  3414  										Name: "Dynamic Port",
  3415  										Fields: []*FieldDiff{
  3416  											{
  3417  												Type: DiffTypeDeleted,
  3418  												Name: "Label",
  3419  												Old:  "bar",
  3420  												New:  "",
  3421  											},
  3422  										},
  3423  									},
  3424  								},
  3425  							},
  3426  						},
  3427  					},
  3428  				},
  3429  			},
  3430  		},
  3431  		{
  3432  			Name: "Config same",
  3433  			Old: &Task{
  3434  				Config: map[string]interface{}{
  3435  					"foo": 1,
  3436  					"bar": "bar",
  3437  					"bam": []string{"a", "b"},
  3438  					"baz": map[string]int{
  3439  						"a": 1,
  3440  						"b": 2,
  3441  					},
  3442  					"boom": &Port{
  3443  						Label: "boom_port",
  3444  					},
  3445  				},
  3446  			},
  3447  			New: &Task{
  3448  				Config: map[string]interface{}{
  3449  					"foo": 1,
  3450  					"bar": "bar",
  3451  					"bam": []string{"a", "b"},
  3452  					"baz": map[string]int{
  3453  						"a": 1,
  3454  						"b": 2,
  3455  					},
  3456  					"boom": &Port{
  3457  						Label: "boom_port",
  3458  					},
  3459  				},
  3460  			},
  3461  			Expected: &TaskDiff{
  3462  				Type: DiffTypeNone,
  3463  			},
  3464  		},
  3465  		{
  3466  			Name: "Config edited",
  3467  			Old: &Task{
  3468  				Config: map[string]interface{}{
  3469  					"foo": 1,
  3470  					"bar": "baz",
  3471  					"bam": []string{"a", "b"},
  3472  					"baz": map[string]int{
  3473  						"a": 1,
  3474  						"b": 2,
  3475  					},
  3476  					"boom": &Port{
  3477  						Label: "boom_port",
  3478  					},
  3479  				},
  3480  			},
  3481  			New: &Task{
  3482  				Config: map[string]interface{}{
  3483  					"foo": 2,
  3484  					"bar": "baz",
  3485  					"bam": []string{"a", "c", "d"},
  3486  					"baz": map[string]int{
  3487  						"b": 3,
  3488  						"c": 4,
  3489  					},
  3490  					"boom": &Port{
  3491  						Label: "boom_port2",
  3492  					},
  3493  				},
  3494  			},
  3495  			Expected: &TaskDiff{
  3496  				Type: DiffTypeEdited,
  3497  				Objects: []*ObjectDiff{
  3498  					{
  3499  						Type: DiffTypeEdited,
  3500  						Name: "Config",
  3501  						Fields: []*FieldDiff{
  3502  							{
  3503  								Type: DiffTypeEdited,
  3504  								Name: "bam[1]",
  3505  								Old:  "b",
  3506  								New:  "c",
  3507  							},
  3508  							{
  3509  								Type: DiffTypeAdded,
  3510  								Name: "bam[2]",
  3511  								Old:  "",
  3512  								New:  "d",
  3513  							},
  3514  							{
  3515  								Type: DiffTypeDeleted,
  3516  								Name: "baz[a]",
  3517  								Old:  "1",
  3518  								New:  "",
  3519  							},
  3520  							{
  3521  								Type: DiffTypeEdited,
  3522  								Name: "baz[b]",
  3523  								Old:  "2",
  3524  								New:  "3",
  3525  							},
  3526  							{
  3527  								Type: DiffTypeAdded,
  3528  								Name: "baz[c]",
  3529  								Old:  "",
  3530  								New:  "4",
  3531  							},
  3532  							{
  3533  								Type: DiffTypeEdited,
  3534  								Name: "boom.Label",
  3535  								Old:  "boom_port",
  3536  								New:  "boom_port2",
  3537  							},
  3538  							{
  3539  								Type: DiffTypeEdited,
  3540  								Name: "foo",
  3541  								Old:  "1",
  3542  								New:  "2",
  3543  							},
  3544  						},
  3545  					},
  3546  				},
  3547  			},
  3548  		},
  3549  		{
  3550  			Name:       "Config edited with context",
  3551  			Contextual: true,
  3552  			Old: &Task{
  3553  				Config: map[string]interface{}{
  3554  					"foo": 1,
  3555  					"bar": "baz",
  3556  					"bam": []string{"a", "b"},
  3557  					"baz": map[string]int{
  3558  						"a": 1,
  3559  						"b": 2,
  3560  					},
  3561  					"boom": &Port{
  3562  						Label: "boom_port",
  3563  					},
  3564  				},
  3565  			},
  3566  			New: &Task{
  3567  				Config: map[string]interface{}{
  3568  					"foo": 2,
  3569  					"bar": "baz",
  3570  					"bam": []string{"a", "c", "d"},
  3571  					"baz": map[string]int{
  3572  						"a": 1,
  3573  						"b": 2,
  3574  					},
  3575  					"boom": &Port{
  3576  						Label: "boom_port",
  3577  					},
  3578  				},
  3579  			},
  3580  			Expected: &TaskDiff{
  3581  				Type: DiffTypeEdited,
  3582  				Objects: []*ObjectDiff{
  3583  					{
  3584  						Type: DiffTypeEdited,
  3585  						Name: "Config",
  3586  						Fields: []*FieldDiff{
  3587  							{
  3588  								Type: DiffTypeNone,
  3589  								Name: "bam[0]",
  3590  								Old:  "a",
  3591  								New:  "a",
  3592  							},
  3593  							{
  3594  								Type: DiffTypeEdited,
  3595  								Name: "bam[1]",
  3596  								Old:  "b",
  3597  								New:  "c",
  3598  							},
  3599  							{
  3600  								Type: DiffTypeAdded,
  3601  								Name: "bam[2]",
  3602  								Old:  "",
  3603  								New:  "d",
  3604  							},
  3605  							{
  3606  								Type: DiffTypeNone,
  3607  								Name: "bar",
  3608  								Old:  "baz",
  3609  								New:  "baz",
  3610  							},
  3611  							{
  3612  								Type: DiffTypeNone,
  3613  								Name: "baz[a]",
  3614  								Old:  "1",
  3615  								New:  "1",
  3616  							},
  3617  							{
  3618  								Type: DiffTypeNone,
  3619  								Name: "baz[b]",
  3620  								Old:  "2",
  3621  								New:  "2",
  3622  							},
  3623  							{
  3624  								Type: DiffTypeNone,
  3625  								Name: "boom.Label",
  3626  								Old:  "boom_port",
  3627  								New:  "boom_port",
  3628  							},
  3629  							{
  3630  								Type: DiffTypeNone,
  3631  								Name: "boom.Value",
  3632  								Old:  "0",
  3633  								New:  "0",
  3634  							},
  3635  							{
  3636  								Type: DiffTypeEdited,
  3637  								Name: "foo",
  3638  								Old:  "1",
  3639  								New:  "2",
  3640  							},
  3641  						},
  3642  					},
  3643  				},
  3644  			},
  3645  		},
  3646  		{
  3647  			Name: "Services edited (no checks)",
  3648  			Old: &Task{
  3649  				Services: []*Service{
  3650  					{
  3651  						Name:      "foo",
  3652  						PortLabel: "foo",
  3653  					},
  3654  					{
  3655  						Name:      "bar",
  3656  						PortLabel: "bar",
  3657  					},
  3658  					{
  3659  						Name:      "baz",
  3660  						PortLabel: "baz",
  3661  					},
  3662  				},
  3663  			},
  3664  			New: &Task{
  3665  				Services: []*Service{
  3666  					{
  3667  						Name:      "bar",
  3668  						PortLabel: "bar",
  3669  					},
  3670  					{
  3671  						Name:      "baz",
  3672  						PortLabel: "baz2",
  3673  					},
  3674  					{
  3675  						Name:      "bam",
  3676  						PortLabel: "bam",
  3677  					},
  3678  				},
  3679  			},
  3680  			Expected: &TaskDiff{
  3681  				Type: DiffTypeEdited,
  3682  				Objects: []*ObjectDiff{
  3683  					{
  3684  						Type: DiffTypeEdited,
  3685  						Name: "Service",
  3686  						Fields: []*FieldDiff{
  3687  							{
  3688  								Type: DiffTypeEdited,
  3689  								Name: "PortLabel",
  3690  								Old:  "baz",
  3691  								New:  "baz2",
  3692  							},
  3693  						},
  3694  					},
  3695  					{
  3696  						Type: DiffTypeAdded,
  3697  						Name: "Service",
  3698  						Fields: []*FieldDiff{
  3699  							{
  3700  								Type: DiffTypeAdded,
  3701  								Name: "Name",
  3702  								Old:  "",
  3703  								New:  "bam",
  3704  							},
  3705  							{
  3706  								Type: DiffTypeAdded,
  3707  								Name: "PortLabel",
  3708  								Old:  "",
  3709  								New:  "bam",
  3710  							},
  3711  						},
  3712  					},
  3713  					{
  3714  						Type: DiffTypeDeleted,
  3715  						Name: "Service",
  3716  						Fields: []*FieldDiff{
  3717  							{
  3718  								Type: DiffTypeDeleted,
  3719  								Name: "Name",
  3720  								Old:  "foo",
  3721  								New:  "",
  3722  							},
  3723  							{
  3724  								Type: DiffTypeDeleted,
  3725  								Name: "PortLabel",
  3726  								Old:  "foo",
  3727  								New:  "",
  3728  							},
  3729  						},
  3730  					},
  3731  				},
  3732  			},
  3733  		},
  3734  		{
  3735  			Name:       "Services edited (no checks) with context",
  3736  			Contextual: true,
  3737  			Old: &Task{
  3738  				Services: []*Service{
  3739  					{
  3740  						Name:      "foo",
  3741  						PortLabel: "foo",
  3742  					},
  3743  				},
  3744  			},
  3745  			New: &Task{
  3746  				Services: []*Service{
  3747  					{
  3748  						Name:        "foo",
  3749  						PortLabel:   "bar",
  3750  						AddressMode: "driver",
  3751  					},
  3752  				},
  3753  			},
  3754  			Expected: &TaskDiff{
  3755  				Type: DiffTypeEdited,
  3756  				Objects: []*ObjectDiff{
  3757  					{
  3758  						Type: DiffTypeEdited,
  3759  						Name: "Service",
  3760  						Fields: []*FieldDiff{
  3761  							{
  3762  								Type: DiffTypeAdded,
  3763  								Name: "AddressMode",
  3764  								New:  "driver",
  3765  							},
  3766  							{
  3767  								Type: DiffTypeNone,
  3768  								Name: "Name",
  3769  								Old:  "foo",
  3770  								New:  "foo",
  3771  							},
  3772  							{
  3773  								Type: DiffTypeEdited,
  3774  								Name: "PortLabel",
  3775  								Old:  "foo",
  3776  								New:  "bar",
  3777  							},
  3778  						},
  3779  					},
  3780  				},
  3781  			},
  3782  		},
  3783  		{
  3784  			Name:       "Services tags edited (no checks) with context",
  3785  			Contextual: true,
  3786  			Old: &Task{
  3787  				Services: []*Service{
  3788  					{
  3789  						Tags:       []string{"foo", "bar"},
  3790  						CanaryTags: []string{"foo", "bar"},
  3791  					},
  3792  				},
  3793  			},
  3794  			New: &Task{
  3795  				Services: []*Service{
  3796  					{
  3797  						Tags:       []string{"bar", "bam"},
  3798  						CanaryTags: []string{"bar", "bam"},
  3799  					},
  3800  				},
  3801  			},
  3802  			Expected: &TaskDiff{
  3803  				Type: DiffTypeEdited,
  3804  				Objects: []*ObjectDiff{
  3805  					{
  3806  						Type: DiffTypeEdited,
  3807  						Name: "Service",
  3808  						Objects: []*ObjectDiff{
  3809  							{
  3810  								Type: DiffTypeEdited,
  3811  								Name: "CanaryTags",
  3812  								Fields: []*FieldDiff{
  3813  									{
  3814  										Type: DiffTypeAdded,
  3815  										Name: "CanaryTags",
  3816  										Old:  "",
  3817  										New:  "bam",
  3818  									},
  3819  									{
  3820  										Type: DiffTypeNone,
  3821  										Name: "CanaryTags",
  3822  										Old:  "bar",
  3823  										New:  "bar",
  3824  									},
  3825  									{
  3826  										Type: DiffTypeDeleted,
  3827  										Name: "CanaryTags",
  3828  										Old:  "foo",
  3829  										New:  "",
  3830  									},
  3831  								},
  3832  							},
  3833  							{
  3834  								Type: DiffTypeEdited,
  3835  								Name: "Tags",
  3836  								Fields: []*FieldDiff{
  3837  									{
  3838  										Type: DiffTypeAdded,
  3839  										Name: "Tags",
  3840  										Old:  "",
  3841  										New:  "bam",
  3842  									},
  3843  									{
  3844  										Type: DiffTypeNone,
  3845  										Name: "Tags",
  3846  										Old:  "bar",
  3847  										New:  "bar",
  3848  									},
  3849  									{
  3850  										Type: DiffTypeDeleted,
  3851  										Name: "Tags",
  3852  										Old:  "foo",
  3853  										New:  "",
  3854  									},
  3855  								},
  3856  							},
  3857  						},
  3858  						Fields: []*FieldDiff{
  3859  							{
  3860  								Type: DiffTypeNone,
  3861  								Name: "AddressMode",
  3862  							},
  3863  							{
  3864  								Type: DiffTypeNone,
  3865  								Name: "Name",
  3866  							},
  3867  							{
  3868  								Type: DiffTypeNone,
  3869  								Name: "PortLabel",
  3870  							},
  3871  						},
  3872  					},
  3873  				},
  3874  			},
  3875  		},
  3876  		{
  3877  			Name: "Service Checks edited",
  3878  			Old: &Task{
  3879  				Services: []*Service{
  3880  					{
  3881  						Name: "foo",
  3882  						Checks: []*ServiceCheck{
  3883  							{
  3884  								Name:     "foo",
  3885  								Type:     "http",
  3886  								Command:  "foo",
  3887  								Args:     []string{"foo"},
  3888  								Path:     "foo",
  3889  								Protocol: "http",
  3890  								Interval: 1 * time.Second,
  3891  								Timeout:  1 * time.Second,
  3892  								Header: map[string][]string{
  3893  									"Foo": {"bar"},
  3894  								},
  3895  							},
  3896  							{
  3897  								Name:     "bar",
  3898  								Type:     "http",
  3899  								Command:  "foo",
  3900  								Args:     []string{"foo"},
  3901  								Path:     "foo",
  3902  								Protocol: "http",
  3903  								Interval: 1 * time.Second,
  3904  								Timeout:  1 * time.Second,
  3905  							},
  3906  							{
  3907  								Name:     "baz",
  3908  								Type:     "http",
  3909  								Command:  "foo",
  3910  								Args:     []string{"foo"},
  3911  								Path:     "foo",
  3912  								Protocol: "http",
  3913  								Interval: 1 * time.Second,
  3914  								Timeout:  1 * time.Second,
  3915  							},
  3916  						},
  3917  					},
  3918  				},
  3919  			},
  3920  			New: &Task{
  3921  				Services: []*Service{
  3922  					{
  3923  						Name: "foo",
  3924  						Checks: []*ServiceCheck{
  3925  							{
  3926  								Name:     "bar",
  3927  								Type:     "http",
  3928  								Command:  "foo",
  3929  								Args:     []string{"foo"},
  3930  								Path:     "foo",
  3931  								Protocol: "http",
  3932  								Interval: 1 * time.Second,
  3933  								Timeout:  1 * time.Second,
  3934  							},
  3935  							{
  3936  								Name:     "baz",
  3937  								Type:     "tcp",
  3938  								Command:  "foo",
  3939  								Args:     []string{"foo"},
  3940  								Path:     "foo",
  3941  								Protocol: "http",
  3942  								Interval: 1 * time.Second,
  3943  								Timeout:  1 * time.Second,
  3944  								Header: map[string][]string{
  3945  									"Eggs": {"spam"},
  3946  								},
  3947  							},
  3948  							{
  3949  								Name:     "bam",
  3950  								Type:     "http",
  3951  								Command:  "foo",
  3952  								Args:     []string{"foo"},
  3953  								Path:     "foo",
  3954  								Protocol: "http",
  3955  								Interval: 1 * time.Second,
  3956  								Timeout:  1 * time.Second,
  3957  							},
  3958  						},
  3959  					},
  3960  				},
  3961  			},
  3962  			Expected: &TaskDiff{
  3963  				Type: DiffTypeEdited,
  3964  				Objects: []*ObjectDiff{
  3965  					{
  3966  						Type: DiffTypeEdited,
  3967  						Name: "Service",
  3968  						Objects: []*ObjectDiff{
  3969  							{
  3970  								Type: DiffTypeEdited,
  3971  								Name: "Check",
  3972  								Fields: []*FieldDiff{
  3973  									{
  3974  										Type: DiffTypeEdited,
  3975  										Name: "Type",
  3976  										Old:  "http",
  3977  										New:  "tcp",
  3978  									},
  3979  								},
  3980  								Objects: []*ObjectDiff{
  3981  									{
  3982  										Type: DiffTypeAdded,
  3983  										Name: "Header",
  3984  										Fields: []*FieldDiff{
  3985  											{
  3986  												Type: DiffTypeAdded,
  3987  												Name: "Eggs[0]",
  3988  												Old:  "",
  3989  												New:  "spam",
  3990  											},
  3991  										},
  3992  									},
  3993  								},
  3994  							},
  3995  							{
  3996  								Type: DiffTypeAdded,
  3997  								Name: "Check",
  3998  								Fields: []*FieldDiff{
  3999  									{
  4000  										Type: DiffTypeAdded,
  4001  										Name: "Command",
  4002  										Old:  "",
  4003  										New:  "foo",
  4004  									},
  4005  									{
  4006  										Type: DiffTypeAdded,
  4007  										Name: "GRPCUseTLS",
  4008  										Old:  "",
  4009  										New:  "false",
  4010  									},
  4011  									{
  4012  										Type: DiffTypeAdded,
  4013  										Name: "Interval",
  4014  										Old:  "",
  4015  										New:  "1000000000",
  4016  									},
  4017  									{
  4018  										Type: DiffTypeAdded,
  4019  										Name: "Name",
  4020  										Old:  "",
  4021  										New:  "bam",
  4022  									},
  4023  									{
  4024  										Type: DiffTypeAdded,
  4025  										Name: "Path",
  4026  										Old:  "",
  4027  										New:  "foo",
  4028  									},
  4029  									{
  4030  										Type: DiffTypeAdded,
  4031  										Name: "Protocol",
  4032  										Old:  "",
  4033  										New:  "http",
  4034  									},
  4035  									{
  4036  										Type: DiffTypeAdded,
  4037  										Name: "TLSSkipVerify",
  4038  										Old:  "",
  4039  										New:  "false",
  4040  									},
  4041  									{
  4042  										Type: DiffTypeAdded,
  4043  										Name: "Timeout",
  4044  										Old:  "",
  4045  										New:  "1000000000",
  4046  									},
  4047  									{
  4048  										Type: DiffTypeAdded,
  4049  										Name: "Type",
  4050  										Old:  "",
  4051  										New:  "http",
  4052  									},
  4053  								},
  4054  							},
  4055  							{
  4056  								Type: DiffTypeDeleted,
  4057  								Name: "Check",
  4058  								Fields: []*FieldDiff{
  4059  									{
  4060  										Type: DiffTypeDeleted,
  4061  										Name: "Command",
  4062  										Old:  "foo",
  4063  										New:  "",
  4064  									},
  4065  									{
  4066  										Type: DiffTypeDeleted,
  4067  										Name: "GRPCUseTLS",
  4068  										Old:  "false",
  4069  										New:  "",
  4070  									},
  4071  									{
  4072  										Type: DiffTypeDeleted,
  4073  										Name: "Interval",
  4074  										Old:  "1000000000",
  4075  										New:  "",
  4076  									},
  4077  									{
  4078  										Type: DiffTypeDeleted,
  4079  										Name: "Name",
  4080  										Old:  "foo",
  4081  										New:  "",
  4082  									},
  4083  									{
  4084  										Type: DiffTypeDeleted,
  4085  										Name: "Path",
  4086  										Old:  "foo",
  4087  										New:  "",
  4088  									},
  4089  									{
  4090  										Type: DiffTypeDeleted,
  4091  										Name: "Protocol",
  4092  										Old:  "http",
  4093  										New:  "",
  4094  									},
  4095  									{
  4096  										Type: DiffTypeDeleted,
  4097  										Name: "TLSSkipVerify",
  4098  										Old:  "false",
  4099  										New:  "",
  4100  									},
  4101  									{
  4102  										Type: DiffTypeDeleted,
  4103  										Name: "Timeout",
  4104  										Old:  "1000000000",
  4105  										New:  "",
  4106  									},
  4107  									{
  4108  										Type: DiffTypeDeleted,
  4109  										Name: "Type",
  4110  										Old:  "http",
  4111  										New:  "",
  4112  									},
  4113  								},
  4114  								Objects: []*ObjectDiff{
  4115  									{
  4116  										Type: DiffTypeDeleted,
  4117  										Name: "Header",
  4118  										Fields: []*FieldDiff{
  4119  											{
  4120  												Type: DiffTypeDeleted,
  4121  												Name: "Foo[0]",
  4122  												Old:  "bar",
  4123  											},
  4124  										},
  4125  									},
  4126  								},
  4127  							},
  4128  						},
  4129  					},
  4130  				},
  4131  			},
  4132  		},
  4133  		{
  4134  			Name:       "Service Checks edited with context",
  4135  			Contextual: true,
  4136  			Old: &Task{
  4137  				Services: []*Service{
  4138  					{
  4139  						Name: "foo",
  4140  						Checks: []*ServiceCheck{
  4141  							{
  4142  								Name:          "foo",
  4143  								Type:          "http",
  4144  								Command:       "foo",
  4145  								Args:          []string{"foo"},
  4146  								Path:          "foo",
  4147  								Protocol:      "http",
  4148  								Interval:      1 * time.Second,
  4149  								Timeout:       1 * time.Second,
  4150  								InitialStatus: "critical",
  4151  								Header: map[string][]string{
  4152  									"Foo": {"bar"},
  4153  								},
  4154  							},
  4155  						},
  4156  					},
  4157  				},
  4158  			},
  4159  			New: &Task{
  4160  				Services: []*Service{
  4161  					{
  4162  						Name: "foo",
  4163  						Checks: []*ServiceCheck{
  4164  							{
  4165  								Name:          "foo",
  4166  								Type:          "tcp",
  4167  								Command:       "foo",
  4168  								Args:          []string{"foo"},
  4169  								Path:          "foo",
  4170  								Protocol:      "http",
  4171  								Interval:      1 * time.Second,
  4172  								Timeout:       1 * time.Second,
  4173  								InitialStatus: "passing",
  4174  								Method:        "POST",
  4175  								Header: map[string][]string{
  4176  									"Foo":  {"bar", "baz"},
  4177  									"Eggs": {"spam"},
  4178  								},
  4179  							},
  4180  						},
  4181  					},
  4182  				},
  4183  			},
  4184  			Expected: &TaskDiff{
  4185  				Type: DiffTypeEdited,
  4186  				Objects: []*ObjectDiff{
  4187  					{
  4188  						Type: DiffTypeEdited,
  4189  						Name: "Service",
  4190  						Fields: []*FieldDiff{
  4191  							{
  4192  								Type: DiffTypeNone,
  4193  								Name: "AddressMode",
  4194  								Old:  "",
  4195  								New:  "",
  4196  							},
  4197  							{
  4198  								Type: DiffTypeNone,
  4199  								Name: "Name",
  4200  								Old:  "foo",
  4201  								New:  "foo",
  4202  							},
  4203  							{
  4204  								Type: DiffTypeNone,
  4205  								Name: "PortLabel",
  4206  								Old:  "",
  4207  								New:  "",
  4208  							},
  4209  						},
  4210  						Objects: []*ObjectDiff{
  4211  							{
  4212  								Type: DiffTypeEdited,
  4213  								Name: "Check",
  4214  								Fields: []*FieldDiff{
  4215  									{
  4216  										Type: DiffTypeNone,
  4217  										Name: "AddressMode",
  4218  										Old:  "",
  4219  										New:  "",
  4220  									},
  4221  									{
  4222  										Type: DiffTypeNone,
  4223  										Name: "Command",
  4224  										Old:  "foo",
  4225  										New:  "foo",
  4226  									},
  4227  									{
  4228  										Type: DiffTypeNone,
  4229  										Name: "GRPCService",
  4230  										Old:  "",
  4231  										New:  "",
  4232  									},
  4233  									{
  4234  										Type: DiffTypeNone,
  4235  										Name: "GRPCUseTLS",
  4236  										Old:  "false",
  4237  										New:  "false",
  4238  									},
  4239  									{
  4240  										Type: DiffTypeEdited,
  4241  										Name: "InitialStatus",
  4242  										Old:  "critical",
  4243  										New:  "passing",
  4244  									},
  4245  									{
  4246  										Type: DiffTypeNone,
  4247  										Name: "Interval",
  4248  										Old:  "1000000000",
  4249  										New:  "1000000000",
  4250  									},
  4251  									{
  4252  										Type: DiffTypeAdded,
  4253  										Name: "Method",
  4254  										Old:  "",
  4255  										New:  "POST",
  4256  									},
  4257  									{
  4258  										Type: DiffTypeNone,
  4259  										Name: "Name",
  4260  										Old:  "foo",
  4261  										New:  "foo",
  4262  									},
  4263  									{
  4264  										Type: DiffTypeNone,
  4265  										Name: "Path",
  4266  										Old:  "foo",
  4267  										New:  "foo",
  4268  									},
  4269  									{
  4270  										Type: DiffTypeNone,
  4271  										Name: "PortLabel",
  4272  										Old:  "",
  4273  										New:  "",
  4274  									},
  4275  									{
  4276  										Type: DiffTypeNone,
  4277  										Name: "Protocol",
  4278  										Old:  "http",
  4279  										New:  "http",
  4280  									},
  4281  									{
  4282  										Type: DiffTypeNone,
  4283  										Name: "TLSSkipVerify",
  4284  										Old:  "false",
  4285  										New:  "false",
  4286  									},
  4287  									{
  4288  										Type: DiffTypeNone,
  4289  										Name: "Timeout",
  4290  										Old:  "1000000000",
  4291  										New:  "1000000000",
  4292  									},
  4293  									{
  4294  										Type: DiffTypeEdited,
  4295  										Name: "Type",
  4296  										Old:  "http",
  4297  										New:  "tcp",
  4298  									},
  4299  								},
  4300  								Objects: []*ObjectDiff{
  4301  									{
  4302  										Type: DiffTypeEdited,
  4303  										Name: "Header",
  4304  										Fields: []*FieldDiff{
  4305  											{
  4306  												Type: DiffTypeAdded,
  4307  												Name: "Eggs[0]",
  4308  												Old:  "",
  4309  												New:  "spam",
  4310  											},
  4311  											{
  4312  												Type: DiffTypeNone,
  4313  												Name: "Foo[0]",
  4314  												Old:  "bar",
  4315  												New:  "bar",
  4316  											},
  4317  											{
  4318  												Type: DiffTypeAdded,
  4319  												Name: "Foo[1]",
  4320  												Old:  "",
  4321  												New:  "baz",
  4322  											},
  4323  										},
  4324  									},
  4325  								},
  4326  							},
  4327  						},
  4328  					},
  4329  				},
  4330  			},
  4331  		},
  4332  		{
  4333  			Name: "CheckRestart edited",
  4334  			Old: &Task{
  4335  				Services: []*Service{
  4336  					{
  4337  						Name: "foo",
  4338  						Checks: []*ServiceCheck{
  4339  							{
  4340  								Name:     "foo",
  4341  								Type:     "http",
  4342  								Command:  "foo",
  4343  								Args:     []string{"foo"},
  4344  								Path:     "foo",
  4345  								Protocol: "http",
  4346  								Interval: 1 * time.Second,
  4347  								Timeout:  1 * time.Second,
  4348  							},
  4349  							{
  4350  								Name:     "bar",
  4351  								Type:     "http",
  4352  								Command:  "foo",
  4353  								Args:     []string{"foo"},
  4354  								Path:     "foo",
  4355  								Protocol: "http",
  4356  								Interval: 1 * time.Second,
  4357  								Timeout:  1 * time.Second,
  4358  								CheckRestart: &CheckRestart{
  4359  									Limit: 2,
  4360  									Grace: 2 * time.Second,
  4361  								},
  4362  							},
  4363  							{
  4364  								Name:     "baz",
  4365  								Type:     "http",
  4366  								Command:  "foo",
  4367  								Args:     []string{"foo"},
  4368  								Path:     "foo",
  4369  								Protocol: "http",
  4370  								Interval: 1 * time.Second,
  4371  								Timeout:  1 * time.Second,
  4372  								CheckRestart: &CheckRestart{
  4373  									Limit: 3,
  4374  									Grace: 3 * time.Second,
  4375  								},
  4376  							},
  4377  						},
  4378  					},
  4379  				},
  4380  			},
  4381  			New: &Task{
  4382  				Services: []*Service{
  4383  					{
  4384  						Name: "foo",
  4385  						Checks: []*ServiceCheck{
  4386  							{
  4387  								Name:     "foo",
  4388  								Type:     "http",
  4389  								Command:  "foo",
  4390  								Args:     []string{"foo"},
  4391  								Path:     "foo",
  4392  								Protocol: "http",
  4393  								Interval: 1 * time.Second,
  4394  								Timeout:  1 * time.Second,
  4395  								CheckRestart: &CheckRestart{
  4396  									Limit: 1,
  4397  									Grace: 1 * time.Second,
  4398  								},
  4399  							},
  4400  							{
  4401  								Name:     "bar",
  4402  								Type:     "http",
  4403  								Command:  "foo",
  4404  								Args:     []string{"foo"},
  4405  								Path:     "foo",
  4406  								Protocol: "http",
  4407  								Interval: 1 * time.Second,
  4408  								Timeout:  1 * time.Second,
  4409  							},
  4410  							{
  4411  								Name:     "baz",
  4412  								Type:     "http",
  4413  								Command:  "foo",
  4414  								Args:     []string{"foo"},
  4415  								Path:     "foo",
  4416  								Protocol: "http",
  4417  								Interval: 1 * time.Second,
  4418  								Timeout:  1 * time.Second,
  4419  								CheckRestart: &CheckRestart{
  4420  									Limit: 4,
  4421  									Grace: 4 * time.Second,
  4422  								},
  4423  							},
  4424  						},
  4425  					},
  4426  				},
  4427  			},
  4428  			Expected: &TaskDiff{
  4429  				Type: DiffTypeEdited,
  4430  				Objects: []*ObjectDiff{
  4431  					{
  4432  						Type: DiffTypeEdited,
  4433  						Name: "Service",
  4434  						Objects: []*ObjectDiff{
  4435  							{
  4436  								Type: DiffTypeEdited,
  4437  								Name: "Check",
  4438  								Objects: []*ObjectDiff{
  4439  									{
  4440  										Type: DiffTypeEdited,
  4441  										Name: "CheckRestart",
  4442  										Fields: []*FieldDiff{
  4443  											{
  4444  												Type: DiffTypeEdited,
  4445  												Name: "Grace",
  4446  												Old:  "3000000000",
  4447  												New:  "4000000000",
  4448  											},
  4449  											{
  4450  												Type: DiffTypeEdited,
  4451  												Name: "Limit",
  4452  												Old:  "3",
  4453  												New:  "4",
  4454  											},
  4455  										},
  4456  									},
  4457  								},
  4458  							},
  4459  							{
  4460  								Type: DiffTypeEdited,
  4461  								Name: "Check",
  4462  								Objects: []*ObjectDiff{
  4463  									{
  4464  										Type: DiffTypeAdded,
  4465  										Name: "CheckRestart",
  4466  										Fields: []*FieldDiff{
  4467  											{
  4468  												Type: DiffTypeAdded,
  4469  												Name: "Grace",
  4470  												New:  "1000000000",
  4471  											},
  4472  											{
  4473  												Type: DiffTypeAdded,
  4474  												Name: "IgnoreWarnings",
  4475  												New:  "false",
  4476  											},
  4477  											{
  4478  												Type: DiffTypeAdded,
  4479  												Name: "Limit",
  4480  												New:  "1",
  4481  											},
  4482  										},
  4483  									},
  4484  								},
  4485  							},
  4486  							{
  4487  								Type: DiffTypeEdited,
  4488  								Name: "Check",
  4489  								Objects: []*ObjectDiff{
  4490  									{
  4491  										Type: DiffTypeDeleted,
  4492  										Name: "CheckRestart",
  4493  										Fields: []*FieldDiff{
  4494  											{
  4495  												Type: DiffTypeDeleted,
  4496  												Name: "Grace",
  4497  												Old:  "2000000000",
  4498  											},
  4499  											{
  4500  												Type: DiffTypeDeleted,
  4501  												Name: "IgnoreWarnings",
  4502  												Old:  "false",
  4503  											},
  4504  											{
  4505  												Type: DiffTypeDeleted,
  4506  												Name: "Limit",
  4507  												Old:  "2",
  4508  											},
  4509  										},
  4510  									},
  4511  								},
  4512  							},
  4513  						},
  4514  					},
  4515  				},
  4516  			},
  4517  		},
  4518  		{
  4519  			Name: "Vault added",
  4520  			Old:  &Task{},
  4521  			New: &Task{
  4522  				Vault: &Vault{
  4523  					Policies:     []string{"foo", "bar"},
  4524  					Env:          true,
  4525  					ChangeMode:   "signal",
  4526  					ChangeSignal: "SIGUSR1",
  4527  				},
  4528  			},
  4529  			Expected: &TaskDiff{
  4530  				Type: DiffTypeEdited,
  4531  				Objects: []*ObjectDiff{
  4532  					{
  4533  						Type: DiffTypeAdded,
  4534  						Name: "Vault",
  4535  						Fields: []*FieldDiff{
  4536  							{
  4537  								Type: DiffTypeAdded,
  4538  								Name: "ChangeMode",
  4539  								Old:  "",
  4540  								New:  "signal",
  4541  							},
  4542  							{
  4543  								Type: DiffTypeAdded,
  4544  								Name: "ChangeSignal",
  4545  								Old:  "",
  4546  								New:  "SIGUSR1",
  4547  							},
  4548  							{
  4549  								Type: DiffTypeAdded,
  4550  								Name: "Env",
  4551  								Old:  "",
  4552  								New:  "true",
  4553  							},
  4554  						},
  4555  						Objects: []*ObjectDiff{
  4556  							{
  4557  								Type: DiffTypeAdded,
  4558  								Name: "Policies",
  4559  								Fields: []*FieldDiff{
  4560  									{
  4561  										Type: DiffTypeAdded,
  4562  										Name: "Policies",
  4563  										Old:  "",
  4564  										New:  "bar",
  4565  									},
  4566  									{
  4567  										Type: DiffTypeAdded,
  4568  										Name: "Policies",
  4569  										Old:  "",
  4570  										New:  "foo",
  4571  									},
  4572  								},
  4573  							},
  4574  						},
  4575  					},
  4576  				},
  4577  			},
  4578  		},
  4579  		{
  4580  			Name: "Vault deleted",
  4581  			Old: &Task{
  4582  				Vault: &Vault{
  4583  					Policies:     []string{"foo", "bar"},
  4584  					Env:          true,
  4585  					ChangeMode:   "signal",
  4586  					ChangeSignal: "SIGUSR1",
  4587  				},
  4588  			},
  4589  			New: &Task{},
  4590  			Expected: &TaskDiff{
  4591  				Type: DiffTypeEdited,
  4592  				Objects: []*ObjectDiff{
  4593  					{
  4594  						Type: DiffTypeDeleted,
  4595  						Name: "Vault",
  4596  						Fields: []*FieldDiff{
  4597  							{
  4598  								Type: DiffTypeDeleted,
  4599  								Name: "ChangeMode",
  4600  								Old:  "signal",
  4601  								New:  "",
  4602  							},
  4603  							{
  4604  								Type: DiffTypeDeleted,
  4605  								Name: "ChangeSignal",
  4606  								Old:  "SIGUSR1",
  4607  								New:  "",
  4608  							},
  4609  							{
  4610  								Type: DiffTypeDeleted,
  4611  								Name: "Env",
  4612  								Old:  "true",
  4613  								New:  "",
  4614  							},
  4615  						},
  4616  						Objects: []*ObjectDiff{
  4617  							{
  4618  								Type: DiffTypeDeleted,
  4619  								Name: "Policies",
  4620  								Fields: []*FieldDiff{
  4621  									{
  4622  										Type: DiffTypeDeleted,
  4623  										Name: "Policies",
  4624  										Old:  "bar",
  4625  										New:  "",
  4626  									},
  4627  									{
  4628  										Type: DiffTypeDeleted,
  4629  										Name: "Policies",
  4630  										Old:  "foo",
  4631  										New:  "",
  4632  									},
  4633  								},
  4634  							},
  4635  						},
  4636  					},
  4637  				},
  4638  			},
  4639  		},
  4640  		{
  4641  			Name: "Vault edited",
  4642  			Old: &Task{
  4643  				Vault: &Vault{
  4644  					Policies:     []string{"foo", "bar"},
  4645  					Env:          true,
  4646  					ChangeMode:   "signal",
  4647  					ChangeSignal: "SIGUSR1",
  4648  				},
  4649  			},
  4650  			New: &Task{
  4651  				Vault: &Vault{
  4652  					Policies:     []string{"bar", "baz"},
  4653  					Env:          false,
  4654  					ChangeMode:   "restart",
  4655  					ChangeSignal: "foo",
  4656  				},
  4657  			},
  4658  			Expected: &TaskDiff{
  4659  				Type: DiffTypeEdited,
  4660  				Objects: []*ObjectDiff{
  4661  					{
  4662  						Type: DiffTypeEdited,
  4663  						Name: "Vault",
  4664  						Fields: []*FieldDiff{
  4665  							{
  4666  								Type: DiffTypeEdited,
  4667  								Name: "ChangeMode",
  4668  								Old:  "signal",
  4669  								New:  "restart",
  4670  							},
  4671  							{
  4672  								Type: DiffTypeEdited,
  4673  								Name: "ChangeSignal",
  4674  								Old:  "SIGUSR1",
  4675  								New:  "foo",
  4676  							},
  4677  							{
  4678  								Type: DiffTypeEdited,
  4679  								Name: "Env",
  4680  								Old:  "true",
  4681  								New:  "false",
  4682  							},
  4683  						},
  4684  						Objects: []*ObjectDiff{
  4685  							{
  4686  								Type: DiffTypeEdited,
  4687  								Name: "Policies",
  4688  								Fields: []*FieldDiff{
  4689  									{
  4690  										Type: DiffTypeAdded,
  4691  										Name: "Policies",
  4692  										Old:  "",
  4693  										New:  "baz",
  4694  									},
  4695  									{
  4696  										Type: DiffTypeDeleted,
  4697  										Name: "Policies",
  4698  										Old:  "foo",
  4699  										New:  "",
  4700  									},
  4701  								},
  4702  							},
  4703  						},
  4704  					},
  4705  				},
  4706  			},
  4707  		},
  4708  		{
  4709  			Name:       "Vault edited with context",
  4710  			Contextual: true,
  4711  			Old: &Task{
  4712  				Vault: &Vault{
  4713  					Policies:     []string{"foo", "bar"},
  4714  					Env:          true,
  4715  					ChangeMode:   "signal",
  4716  					ChangeSignal: "SIGUSR1",
  4717  				},
  4718  			},
  4719  			New: &Task{
  4720  				Vault: &Vault{
  4721  					Policies:     []string{"bar", "baz"},
  4722  					Env:          true,
  4723  					ChangeMode:   "signal",
  4724  					ChangeSignal: "SIGUSR1",
  4725  				},
  4726  			},
  4727  			Expected: &TaskDiff{
  4728  				Type: DiffTypeEdited,
  4729  				Objects: []*ObjectDiff{
  4730  					{
  4731  						Type: DiffTypeEdited,
  4732  						Name: "Vault",
  4733  						Fields: []*FieldDiff{
  4734  							{
  4735  								Type: DiffTypeNone,
  4736  								Name: "ChangeMode",
  4737  								Old:  "signal",
  4738  								New:  "signal",
  4739  							},
  4740  							{
  4741  								Type: DiffTypeNone,
  4742  								Name: "ChangeSignal",
  4743  								Old:  "SIGUSR1",
  4744  								New:  "SIGUSR1",
  4745  							},
  4746  							{
  4747  								Type: DiffTypeNone,
  4748  								Name: "Env",
  4749  								Old:  "true",
  4750  								New:  "true",
  4751  							},
  4752  						},
  4753  						Objects: []*ObjectDiff{
  4754  							{
  4755  								Type: DiffTypeEdited,
  4756  								Name: "Policies",
  4757  								Fields: []*FieldDiff{
  4758  									{
  4759  										Type: DiffTypeAdded,
  4760  										Name: "Policies",
  4761  										Old:  "",
  4762  										New:  "baz",
  4763  									},
  4764  									{
  4765  										Type: DiffTypeNone,
  4766  										Name: "Policies",
  4767  										Old:  "bar",
  4768  										New:  "bar",
  4769  									},
  4770  									{
  4771  										Type: DiffTypeDeleted,
  4772  										Name: "Policies",
  4773  										Old:  "foo",
  4774  										New:  "",
  4775  									},
  4776  								},
  4777  							},
  4778  						},
  4779  					},
  4780  				},
  4781  			},
  4782  		},
  4783  		{
  4784  			Name: "Template edited",
  4785  			Old: &Task{
  4786  				Templates: []*Template{
  4787  					{
  4788  						SourcePath:   "foo",
  4789  						DestPath:     "bar",
  4790  						EmbeddedTmpl: "baz",
  4791  						ChangeMode:   "bam",
  4792  						ChangeSignal: "SIGHUP",
  4793  						Splay:        1,
  4794  						Perms:        "0644",
  4795  						VaultGrace:   3 * time.Second,
  4796  					},
  4797  					{
  4798  						SourcePath:   "foo2",
  4799  						DestPath:     "bar2",
  4800  						EmbeddedTmpl: "baz2",
  4801  						ChangeMode:   "bam2",
  4802  						ChangeSignal: "SIGHUP2",
  4803  						Splay:        2,
  4804  						Perms:        "0666",
  4805  						Envvars:      true,
  4806  						VaultGrace:   5 * time.Second,
  4807  					},
  4808  				},
  4809  			},
  4810  			New: &Task{
  4811  				Templates: []*Template{
  4812  					{
  4813  						SourcePath:   "foo",
  4814  						DestPath:     "bar",
  4815  						EmbeddedTmpl: "baz",
  4816  						ChangeMode:   "bam",
  4817  						ChangeSignal: "SIGHUP",
  4818  						Splay:        1,
  4819  						Perms:        "0644",
  4820  						VaultGrace:   3 * time.Second,
  4821  					},
  4822  					{
  4823  						SourcePath:   "foo3",
  4824  						DestPath:     "bar3",
  4825  						EmbeddedTmpl: "baz3",
  4826  						ChangeMode:   "bam3",
  4827  						ChangeSignal: "SIGHUP3",
  4828  						Splay:        3,
  4829  						Perms:        "0776",
  4830  						VaultGrace:   10 * time.Second,
  4831  					},
  4832  				},
  4833  			},
  4834  			Expected: &TaskDiff{
  4835  				Type: DiffTypeEdited,
  4836  				Objects: []*ObjectDiff{
  4837  					{
  4838  						Type: DiffTypeAdded,
  4839  						Name: "Template",
  4840  						Fields: []*FieldDiff{
  4841  							{
  4842  								Type: DiffTypeAdded,
  4843  								Name: "ChangeMode",
  4844  								Old:  "",
  4845  								New:  "bam3",
  4846  							},
  4847  							{
  4848  								Type: DiffTypeAdded,
  4849  								Name: "ChangeSignal",
  4850  								Old:  "",
  4851  								New:  "SIGHUP3",
  4852  							},
  4853  							{
  4854  								Type: DiffTypeAdded,
  4855  								Name: "DestPath",
  4856  								Old:  "",
  4857  								New:  "bar3",
  4858  							},
  4859  							{
  4860  								Type: DiffTypeAdded,
  4861  								Name: "EmbeddedTmpl",
  4862  								Old:  "",
  4863  								New:  "baz3",
  4864  							},
  4865  							{
  4866  								Type: DiffTypeAdded,
  4867  								Name: "Envvars",
  4868  								Old:  "",
  4869  								New:  "false",
  4870  							},
  4871  							{
  4872  								Type: DiffTypeAdded,
  4873  								Name: "Perms",
  4874  								Old:  "",
  4875  								New:  "0776",
  4876  							},
  4877  							{
  4878  								Type: DiffTypeAdded,
  4879  								Name: "SourcePath",
  4880  								Old:  "",
  4881  								New:  "foo3",
  4882  							},
  4883  							{
  4884  								Type: DiffTypeAdded,
  4885  								Name: "Splay",
  4886  								Old:  "",
  4887  								New:  "3",
  4888  							},
  4889  							{
  4890  								Type: DiffTypeAdded,
  4891  								Name: "VaultGrace",
  4892  								Old:  "",
  4893  								New:  "10000000000",
  4894  							},
  4895  						},
  4896  					},
  4897  					{
  4898  						Type: DiffTypeDeleted,
  4899  						Name: "Template",
  4900  						Fields: []*FieldDiff{
  4901  							{
  4902  								Type: DiffTypeDeleted,
  4903  								Name: "ChangeMode",
  4904  								Old:  "bam2",
  4905  								New:  "",
  4906  							},
  4907  							{
  4908  								Type: DiffTypeDeleted,
  4909  								Name: "ChangeSignal",
  4910  								Old:  "SIGHUP2",
  4911  								New:  "",
  4912  							},
  4913  							{
  4914  								Type: DiffTypeDeleted,
  4915  								Name: "DestPath",
  4916  								Old:  "bar2",
  4917  								New:  "",
  4918  							},
  4919  							{
  4920  								Type: DiffTypeDeleted,
  4921  								Name: "EmbeddedTmpl",
  4922  								Old:  "baz2",
  4923  								New:  "",
  4924  							},
  4925  							{
  4926  								Type: DiffTypeDeleted,
  4927  								Name: "Envvars",
  4928  								Old:  "true",
  4929  								New:  "",
  4930  							},
  4931  							{
  4932  								Type: DiffTypeDeleted,
  4933  								Name: "Perms",
  4934  								Old:  "0666",
  4935  								New:  "",
  4936  							},
  4937  							{
  4938  								Type: DiffTypeDeleted,
  4939  								Name: "SourcePath",
  4940  								Old:  "foo2",
  4941  								New:  "",
  4942  							},
  4943  							{
  4944  								Type: DiffTypeDeleted,
  4945  								Name: "Splay",
  4946  								Old:  "2",
  4947  								New:  "",
  4948  							},
  4949  							{
  4950  								Type: DiffTypeDeleted,
  4951  								Name: "VaultGrace",
  4952  								Old:  "5000000000",
  4953  								New:  "",
  4954  							},
  4955  						},
  4956  					},
  4957  				},
  4958  			},
  4959  		},
  4960  		{
  4961  			Name: "DispatchPayload added",
  4962  			Old:  &Task{},
  4963  			New: &Task{
  4964  				DispatchPayload: &DispatchPayloadConfig{
  4965  					File: "foo",
  4966  				},
  4967  			},
  4968  			Expected: &TaskDiff{
  4969  				Type: DiffTypeEdited,
  4970  				Objects: []*ObjectDiff{
  4971  					{
  4972  						Type: DiffTypeAdded,
  4973  						Name: "DispatchPayload",
  4974  						Fields: []*FieldDiff{
  4975  							{
  4976  								Type: DiffTypeAdded,
  4977  								Name: "File",
  4978  								Old:  "",
  4979  								New:  "foo",
  4980  							},
  4981  						},
  4982  					},
  4983  				},
  4984  			},
  4985  		},
  4986  		{
  4987  			Name: "DispatchPayload deleted",
  4988  			Old: &Task{
  4989  				DispatchPayload: &DispatchPayloadConfig{
  4990  					File: "foo",
  4991  				},
  4992  			},
  4993  			New: &Task{},
  4994  			Expected: &TaskDiff{
  4995  				Type: DiffTypeEdited,
  4996  				Objects: []*ObjectDiff{
  4997  					{
  4998  						Type: DiffTypeDeleted,
  4999  						Name: "DispatchPayload",
  5000  						Fields: []*FieldDiff{
  5001  							{
  5002  								Type: DiffTypeDeleted,
  5003  								Name: "File",
  5004  								Old:  "foo",
  5005  								New:  "",
  5006  							},
  5007  						},
  5008  					},
  5009  				},
  5010  			},
  5011  		},
  5012  		{
  5013  			Name: "Dispatch payload edited",
  5014  			Old: &Task{
  5015  				DispatchPayload: &DispatchPayloadConfig{
  5016  					File: "foo",
  5017  				},
  5018  			},
  5019  			New: &Task{
  5020  				DispatchPayload: &DispatchPayloadConfig{
  5021  					File: "bar",
  5022  				},
  5023  			},
  5024  			Expected: &TaskDiff{
  5025  				Type: DiffTypeEdited,
  5026  				Objects: []*ObjectDiff{
  5027  					{
  5028  						Type: DiffTypeEdited,
  5029  						Name: "DispatchPayload",
  5030  						Fields: []*FieldDiff{
  5031  							{
  5032  								Type: DiffTypeEdited,
  5033  								Name: "File",
  5034  								Old:  "foo",
  5035  								New:  "bar",
  5036  							},
  5037  						},
  5038  					},
  5039  				},
  5040  			},
  5041  		},
  5042  		{
  5043  			// Place holder for if more fields are added
  5044  			Name:       "DispatchPayload edited with context",
  5045  			Contextual: true,
  5046  			Old: &Task{
  5047  				DispatchPayload: &DispatchPayloadConfig{
  5048  					File: "foo",
  5049  				},
  5050  			},
  5051  			New: &Task{
  5052  				DispatchPayload: &DispatchPayloadConfig{
  5053  					File: "bar",
  5054  				},
  5055  			},
  5056  			Expected: &TaskDiff{
  5057  				Type: DiffTypeEdited,
  5058  				Objects: []*ObjectDiff{
  5059  					{
  5060  						Type: DiffTypeEdited,
  5061  						Name: "DispatchPayload",
  5062  						Fields: []*FieldDiff{
  5063  							{
  5064  								Type: DiffTypeEdited,
  5065  								Name: "File",
  5066  								Old:  "foo",
  5067  								New:  "bar",
  5068  							},
  5069  						},
  5070  					},
  5071  				},
  5072  			},
  5073  		},
  5074  	}
  5075  
  5076  	for i, c := range cases {
  5077  		t.Run(c.Name, func(t *testing.T) {
  5078  			actual, err := c.Old.Diff(c.New, c.Contextual)
  5079  			if c.Error && err == nil {
  5080  				t.Fatalf("case %d: expected errored", i+1)
  5081  			} else if err != nil {
  5082  				if !c.Error {
  5083  					t.Fatalf("case %d: errored %#v", i+1, err)
  5084  				} else {
  5085  					return
  5086  				}
  5087  			}
  5088  
  5089  			if !reflect.DeepEqual(actual, c.Expected) {
  5090  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  5091  					i+1, actual, c.Expected)
  5092  			}
  5093  		})
  5094  	}
  5095  }