github.com/emate/nomad@v0.8.2-wo-binpacking/nomad/structs/diff_test.go (about)

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