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