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