github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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  			// Update strategy edited
   401  			Old: &Job{
   402  				Update: UpdateStrategy{
   403  					Stagger:     10 * time.Second,
   404  					MaxParallel: 5,
   405  				},
   406  			},
   407  			New: &Job{
   408  				Update: UpdateStrategy{
   409  					Stagger:     60 * time.Second,
   410  					MaxParallel: 10,
   411  				},
   412  			},
   413  			Expected: &JobDiff{
   414  				Type: DiffTypeEdited,
   415  				Objects: []*ObjectDiff{
   416  					{
   417  						Type: DiffTypeEdited,
   418  						Name: "Update",
   419  						Fields: []*FieldDiff{
   420  							{
   421  								Type: DiffTypeEdited,
   422  								Name: "MaxParallel",
   423  								Old:  "5",
   424  								New:  "10",
   425  							},
   426  							{
   427  								Type: DiffTypeEdited,
   428  								Name: "Stagger",
   429  								Old:  "10000000000",
   430  								New:  "60000000000",
   431  							},
   432  						},
   433  					},
   434  				},
   435  			},
   436  		},
   437  		{
   438  			// Update strategy edited with context
   439  			Contextual: true,
   440  			Old: &Job{
   441  				Update: UpdateStrategy{
   442  					Stagger:     10 * time.Second,
   443  					MaxParallel: 5,
   444  				},
   445  			},
   446  			New: &Job{
   447  				Update: UpdateStrategy{
   448  					Stagger:     60 * time.Second,
   449  					MaxParallel: 5,
   450  				},
   451  			},
   452  			Expected: &JobDiff{
   453  				Type: DiffTypeEdited,
   454  				Objects: []*ObjectDiff{
   455  					{
   456  						Type: DiffTypeEdited,
   457  						Name: "Update",
   458  						Fields: []*FieldDiff{
   459  							{
   460  								Type: DiffTypeNone,
   461  								Name: "MaxParallel",
   462  								Old:  "5",
   463  								New:  "5",
   464  							},
   465  							{
   466  								Type: DiffTypeEdited,
   467  								Name: "Stagger",
   468  								Old:  "10000000000",
   469  								New:  "60000000000",
   470  							},
   471  						},
   472  					},
   473  				},
   474  			},
   475  		},
   476  		{
   477  			// Periodic added
   478  			Old: &Job{},
   479  			New: &Job{
   480  				Periodic: &PeriodicConfig{
   481  					Enabled:         false,
   482  					Spec:            "*/15 * * * * *",
   483  					SpecType:        "foo",
   484  					ProhibitOverlap: false,
   485  				},
   486  			},
   487  			Expected: &JobDiff{
   488  				Type: DiffTypeEdited,
   489  				Objects: []*ObjectDiff{
   490  					{
   491  						Type: DiffTypeAdded,
   492  						Name: "Periodic",
   493  						Fields: []*FieldDiff{
   494  							{
   495  								Type: DiffTypeAdded,
   496  								Name: "Enabled",
   497  								Old:  "",
   498  								New:  "false",
   499  							},
   500  							{
   501  								Type: DiffTypeAdded,
   502  								Name: "ProhibitOverlap",
   503  								Old:  "",
   504  								New:  "false",
   505  							},
   506  							{
   507  								Type: DiffTypeAdded,
   508  								Name: "Spec",
   509  								Old:  "",
   510  								New:  "*/15 * * * * *",
   511  							},
   512  							{
   513  								Type: DiffTypeAdded,
   514  								Name: "SpecType",
   515  								Old:  "",
   516  								New:  "foo",
   517  							},
   518  						},
   519  					},
   520  				},
   521  			},
   522  		},
   523  		{
   524  			// Periodic deleted
   525  			Old: &Job{
   526  				Periodic: &PeriodicConfig{
   527  					Enabled:         false,
   528  					Spec:            "*/15 * * * * *",
   529  					SpecType:        "foo",
   530  					ProhibitOverlap: false,
   531  				},
   532  			},
   533  			New: &Job{},
   534  			Expected: &JobDiff{
   535  				Type: DiffTypeEdited,
   536  				Objects: []*ObjectDiff{
   537  					{
   538  						Type: DiffTypeDeleted,
   539  						Name: "Periodic",
   540  						Fields: []*FieldDiff{
   541  							{
   542  								Type: DiffTypeDeleted,
   543  								Name: "Enabled",
   544  								Old:  "false",
   545  								New:  "",
   546  							},
   547  							{
   548  								Type: DiffTypeDeleted,
   549  								Name: "ProhibitOverlap",
   550  								Old:  "false",
   551  								New:  "",
   552  							},
   553  							{
   554  								Type: DiffTypeDeleted,
   555  								Name: "Spec",
   556  								Old:  "*/15 * * * * *",
   557  								New:  "",
   558  							},
   559  							{
   560  								Type: DiffTypeDeleted,
   561  								Name: "SpecType",
   562  								Old:  "foo",
   563  								New:  "",
   564  							},
   565  						},
   566  					},
   567  				},
   568  			},
   569  		},
   570  		{
   571  			// Periodic edited
   572  			Old: &Job{
   573  				Periodic: &PeriodicConfig{
   574  					Enabled:         false,
   575  					Spec:            "*/15 * * * * *",
   576  					SpecType:        "foo",
   577  					ProhibitOverlap: false,
   578  				},
   579  			},
   580  			New: &Job{
   581  				Periodic: &PeriodicConfig{
   582  					Enabled:         true,
   583  					Spec:            "* * * * * *",
   584  					SpecType:        "cron",
   585  					ProhibitOverlap: true,
   586  				},
   587  			},
   588  			Expected: &JobDiff{
   589  				Type: DiffTypeEdited,
   590  				Objects: []*ObjectDiff{
   591  					{
   592  						Type: DiffTypeEdited,
   593  						Name: "Periodic",
   594  						Fields: []*FieldDiff{
   595  							{
   596  								Type: DiffTypeEdited,
   597  								Name: "Enabled",
   598  								Old:  "false",
   599  								New:  "true",
   600  							},
   601  							{
   602  								Type: DiffTypeEdited,
   603  								Name: "ProhibitOverlap",
   604  								Old:  "false",
   605  								New:  "true",
   606  							},
   607  							{
   608  								Type: DiffTypeEdited,
   609  								Name: "Spec",
   610  								Old:  "*/15 * * * * *",
   611  								New:  "* * * * * *",
   612  							},
   613  							{
   614  								Type: DiffTypeEdited,
   615  								Name: "SpecType",
   616  								Old:  "foo",
   617  								New:  "cron",
   618  							},
   619  						},
   620  					},
   621  				},
   622  			},
   623  		},
   624  		{
   625  			// Periodic edited with context
   626  			Contextual: true,
   627  			Old: &Job{
   628  				Periodic: &PeriodicConfig{
   629  					Enabled:         false,
   630  					Spec:            "*/15 * * * * *",
   631  					SpecType:        "foo",
   632  					ProhibitOverlap: false,
   633  				},
   634  			},
   635  			New: &Job{
   636  				Periodic: &PeriodicConfig{
   637  					Enabled:         true,
   638  					Spec:            "* * * * * *",
   639  					SpecType:        "foo",
   640  					ProhibitOverlap: false,
   641  				},
   642  			},
   643  			Expected: &JobDiff{
   644  				Type: DiffTypeEdited,
   645  				Objects: []*ObjectDiff{
   646  					{
   647  						Type: DiffTypeEdited,
   648  						Name: "Periodic",
   649  						Fields: []*FieldDiff{
   650  							{
   651  								Type: DiffTypeEdited,
   652  								Name: "Enabled",
   653  								Old:  "false",
   654  								New:  "true",
   655  							},
   656  							{
   657  								Type: DiffTypeNone,
   658  								Name: "ProhibitOverlap",
   659  								Old:  "false",
   660  								New:  "false",
   661  							},
   662  							{
   663  								Type: DiffTypeEdited,
   664  								Name: "Spec",
   665  								Old:  "*/15 * * * * *",
   666  								New:  "* * * * * *",
   667  							},
   668  							{
   669  								Type: DiffTypeNone,
   670  								Name: "SpecType",
   671  								Old:  "foo",
   672  								New:  "foo",
   673  							},
   674  						},
   675  					},
   676  				},
   677  			},
   678  		},
   679  		{
   680  			// Constraints edited
   681  			Old: &Job{
   682  				Constraints: []*Constraint{
   683  					{
   684  						LTarget: "foo",
   685  						RTarget: "foo",
   686  						Operand: "foo",
   687  						str:     "foo",
   688  					},
   689  					{
   690  						LTarget: "bar",
   691  						RTarget: "bar",
   692  						Operand: "bar",
   693  						str:     "bar",
   694  					},
   695  				},
   696  			},
   697  			New: &Job{
   698  				Constraints: []*Constraint{
   699  					{
   700  						LTarget: "foo",
   701  						RTarget: "foo",
   702  						Operand: "foo",
   703  						str:     "foo",
   704  					},
   705  					{
   706  						LTarget: "baz",
   707  						RTarget: "baz",
   708  						Operand: "baz",
   709  						str:     "baz",
   710  					},
   711  				},
   712  			},
   713  			Expected: &JobDiff{
   714  				Type: DiffTypeEdited,
   715  				Objects: []*ObjectDiff{
   716  					{
   717  						Type: DiffTypeAdded,
   718  						Name: "Constraint",
   719  						Fields: []*FieldDiff{
   720  							{
   721  								Type: DiffTypeAdded,
   722  								Name: "LTarget",
   723  								Old:  "",
   724  								New:  "baz",
   725  							},
   726  							{
   727  								Type: DiffTypeAdded,
   728  								Name: "Operand",
   729  								Old:  "",
   730  								New:  "baz",
   731  							},
   732  							{
   733  								Type: DiffTypeAdded,
   734  								Name: "RTarget",
   735  								Old:  "",
   736  								New:  "baz",
   737  							},
   738  						},
   739  					},
   740  					{
   741  						Type: DiffTypeDeleted,
   742  						Name: "Constraint",
   743  						Fields: []*FieldDiff{
   744  							{
   745  								Type: DiffTypeDeleted,
   746  								Name: "LTarget",
   747  								Old:  "bar",
   748  								New:  "",
   749  							},
   750  							{
   751  								Type: DiffTypeDeleted,
   752  								Name: "Operand",
   753  								Old:  "bar",
   754  								New:  "",
   755  							},
   756  							{
   757  								Type: DiffTypeDeleted,
   758  								Name: "RTarget",
   759  								Old:  "bar",
   760  								New:  "",
   761  							},
   762  						},
   763  					},
   764  				},
   765  			},
   766  		},
   767  		{
   768  			// Task groups edited
   769  			Old: &Job{
   770  				TaskGroups: []*TaskGroup{
   771  					{
   772  						Name:  "foo",
   773  						Count: 1,
   774  					},
   775  					{
   776  						Name:  "bar",
   777  						Count: 1,
   778  					},
   779  					{
   780  						Name:  "baz",
   781  						Count: 1,
   782  					},
   783  				},
   784  			},
   785  			New: &Job{
   786  				TaskGroups: []*TaskGroup{
   787  					{
   788  						Name:  "bar",
   789  						Count: 1,
   790  					},
   791  					{
   792  						Name:  "baz",
   793  						Count: 2,
   794  					},
   795  					{
   796  						Name:  "bam",
   797  						Count: 1,
   798  					},
   799  				},
   800  			},
   801  			Expected: &JobDiff{
   802  				Type: DiffTypeEdited,
   803  				TaskGroups: []*TaskGroupDiff{
   804  					{
   805  						Type: DiffTypeAdded,
   806  						Name: "bam",
   807  						Fields: []*FieldDiff{
   808  							{
   809  								Type: DiffTypeAdded,
   810  								Name: "Count",
   811  								Old:  "",
   812  								New:  "1",
   813  							},
   814  						},
   815  					},
   816  					{
   817  						Type: DiffTypeNone,
   818  						Name: "bar",
   819  					},
   820  					{
   821  						Type: DiffTypeEdited,
   822  						Name: "baz",
   823  						Fields: []*FieldDiff{
   824  							{
   825  								Type: DiffTypeEdited,
   826  								Name: "Count",
   827  								Old:  "1",
   828  								New:  "2",
   829  							},
   830  						},
   831  					},
   832  					{
   833  						Type: DiffTypeDeleted,
   834  						Name: "foo",
   835  						Fields: []*FieldDiff{
   836  							{
   837  								Type: DiffTypeDeleted,
   838  								Name: "Count",
   839  								Old:  "1",
   840  								New:  "",
   841  							},
   842  						},
   843  					},
   844  				},
   845  			},
   846  		},
   847  	}
   848  
   849  	for i, c := range cases {
   850  		actual, err := c.Old.Diff(c.New, c.Contextual)
   851  		if c.Error && err == nil {
   852  			t.Fatalf("case %d: expected errored")
   853  		} else if err != nil {
   854  			if !c.Error {
   855  				t.Fatalf("case %d: errored %#v", i+1, err)
   856  			} else {
   857  				continue
   858  			}
   859  		}
   860  
   861  		if !reflect.DeepEqual(actual, c.Expected) {
   862  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
   863  				i+1, actual, c.Expected)
   864  		}
   865  	}
   866  }
   867  
   868  func TestTaskGroupDiff(t *testing.T) {
   869  	cases := []struct {
   870  		Old, New   *TaskGroup
   871  		Expected   *TaskGroupDiff
   872  		Error      bool
   873  		Contextual bool
   874  	}{
   875  		{
   876  			Old: nil,
   877  			New: nil,
   878  			Expected: &TaskGroupDiff{
   879  				Type: DiffTypeNone,
   880  			},
   881  		},
   882  		{
   883  			// Primitive only that has different names
   884  			Old: &TaskGroup{
   885  				Name:  "foo",
   886  				Count: 10,
   887  				Meta: map[string]string{
   888  					"foo": "bar",
   889  				},
   890  			},
   891  			New: &TaskGroup{
   892  				Name:  "bar",
   893  				Count: 10,
   894  				Meta: map[string]string{
   895  					"foo": "bar",
   896  				},
   897  			},
   898  			Error: true,
   899  		},
   900  		{
   901  			// Primitive only that is the same
   902  			Old: &TaskGroup{
   903  				Name:  "foo",
   904  				Count: 10,
   905  				Meta: map[string]string{
   906  					"foo": "bar",
   907  				},
   908  			},
   909  			New: &TaskGroup{
   910  				Name:  "foo",
   911  				Count: 10,
   912  				Meta: map[string]string{
   913  					"foo": "bar",
   914  				},
   915  			},
   916  			Expected: &TaskGroupDiff{
   917  				Type: DiffTypeNone,
   918  				Name: "foo",
   919  			},
   920  		},
   921  		{
   922  			// Primitive only that has diffs
   923  			Old: &TaskGroup{
   924  				Name:  "foo",
   925  				Count: 10,
   926  				Meta: map[string]string{
   927  					"foo": "bar",
   928  				},
   929  			},
   930  			New: &TaskGroup{
   931  				Name:  "foo",
   932  				Count: 100,
   933  				Meta: map[string]string{
   934  					"foo": "baz",
   935  				},
   936  			},
   937  			Expected: &TaskGroupDiff{
   938  				Type: DiffTypeEdited,
   939  				Name: "foo",
   940  				Fields: []*FieldDiff{
   941  					{
   942  						Type: DiffTypeEdited,
   943  						Name: "Count",
   944  						Old:  "10",
   945  						New:  "100",
   946  					},
   947  					{
   948  						Type: DiffTypeEdited,
   949  						Name: "Meta[foo]",
   950  						Old:  "bar",
   951  						New:  "baz",
   952  					},
   953  				},
   954  			},
   955  		},
   956  		{
   957  			// Map diff
   958  			Old: &TaskGroup{
   959  				Meta: map[string]string{
   960  					"foo": "foo",
   961  					"bar": "bar",
   962  				},
   963  			},
   964  			New: &TaskGroup{
   965  				Meta: map[string]string{
   966  					"bar": "bar",
   967  					"baz": "baz",
   968  				},
   969  			},
   970  			Expected: &TaskGroupDiff{
   971  				Type: DiffTypeEdited,
   972  				Fields: []*FieldDiff{
   973  					{
   974  						Type: DiffTypeAdded,
   975  						Name: "Meta[baz]",
   976  						Old:  "",
   977  						New:  "baz",
   978  					},
   979  					{
   980  						Type: DiffTypeDeleted,
   981  						Name: "Meta[foo]",
   982  						Old:  "foo",
   983  						New:  "",
   984  					},
   985  				},
   986  			},
   987  		},
   988  		{
   989  			// Constraints edited
   990  			Old: &TaskGroup{
   991  				Constraints: []*Constraint{
   992  					{
   993  						LTarget: "foo",
   994  						RTarget: "foo",
   995  						Operand: "foo",
   996  						str:     "foo",
   997  					},
   998  					{
   999  						LTarget: "bar",
  1000  						RTarget: "bar",
  1001  						Operand: "bar",
  1002  						str:     "bar",
  1003  					},
  1004  				},
  1005  			},
  1006  			New: &TaskGroup{
  1007  				Constraints: []*Constraint{
  1008  					{
  1009  						LTarget: "foo",
  1010  						RTarget: "foo",
  1011  						Operand: "foo",
  1012  						str:     "foo",
  1013  					},
  1014  					{
  1015  						LTarget: "baz",
  1016  						RTarget: "baz",
  1017  						Operand: "baz",
  1018  						str:     "baz",
  1019  					},
  1020  				},
  1021  			},
  1022  			Expected: &TaskGroupDiff{
  1023  				Type: DiffTypeEdited,
  1024  				Objects: []*ObjectDiff{
  1025  					{
  1026  						Type: DiffTypeAdded,
  1027  						Name: "Constraint",
  1028  						Fields: []*FieldDiff{
  1029  							{
  1030  								Type: DiffTypeAdded,
  1031  								Name: "LTarget",
  1032  								Old:  "",
  1033  								New:  "baz",
  1034  							},
  1035  							{
  1036  								Type: DiffTypeAdded,
  1037  								Name: "Operand",
  1038  								Old:  "",
  1039  								New:  "baz",
  1040  							},
  1041  							{
  1042  								Type: DiffTypeAdded,
  1043  								Name: "RTarget",
  1044  								Old:  "",
  1045  								New:  "baz",
  1046  							},
  1047  						},
  1048  					},
  1049  					{
  1050  						Type: DiffTypeDeleted,
  1051  						Name: "Constraint",
  1052  						Fields: []*FieldDiff{
  1053  							{
  1054  								Type: DiffTypeDeleted,
  1055  								Name: "LTarget",
  1056  								Old:  "bar",
  1057  								New:  "",
  1058  							},
  1059  							{
  1060  								Type: DiffTypeDeleted,
  1061  								Name: "Operand",
  1062  								Old:  "bar",
  1063  								New:  "",
  1064  							},
  1065  							{
  1066  								Type: DiffTypeDeleted,
  1067  								Name: "RTarget",
  1068  								Old:  "bar",
  1069  								New:  "",
  1070  							},
  1071  						},
  1072  					},
  1073  				},
  1074  			},
  1075  		},
  1076  		{
  1077  			// RestartPolicy added
  1078  			Old: &TaskGroup{},
  1079  			New: &TaskGroup{
  1080  				RestartPolicy: &RestartPolicy{
  1081  					Attempts: 1,
  1082  					Interval: 1 * time.Second,
  1083  					Delay:    1 * time.Second,
  1084  					Mode:     "fail",
  1085  				},
  1086  			},
  1087  			Expected: &TaskGroupDiff{
  1088  				Type: DiffTypeEdited,
  1089  				Objects: []*ObjectDiff{
  1090  					{
  1091  						Type: DiffTypeAdded,
  1092  						Name: "RestartPolicy",
  1093  						Fields: []*FieldDiff{
  1094  							{
  1095  								Type: DiffTypeAdded,
  1096  								Name: "Attempts",
  1097  								Old:  "",
  1098  								New:  "1",
  1099  							},
  1100  							{
  1101  								Type: DiffTypeAdded,
  1102  								Name: "Delay",
  1103  								Old:  "",
  1104  								New:  "1000000000",
  1105  							},
  1106  							{
  1107  								Type: DiffTypeAdded,
  1108  								Name: "Interval",
  1109  								Old:  "",
  1110  								New:  "1000000000",
  1111  							},
  1112  							{
  1113  								Type: DiffTypeAdded,
  1114  								Name: "Mode",
  1115  								Old:  "",
  1116  								New:  "fail",
  1117  							},
  1118  						},
  1119  					},
  1120  				},
  1121  			},
  1122  		},
  1123  		{
  1124  			// RestartPolicy deleted
  1125  			Old: &TaskGroup{
  1126  				RestartPolicy: &RestartPolicy{
  1127  					Attempts: 1,
  1128  					Interval: 1 * time.Second,
  1129  					Delay:    1 * time.Second,
  1130  					Mode:     "fail",
  1131  				},
  1132  			},
  1133  			New: &TaskGroup{},
  1134  			Expected: &TaskGroupDiff{
  1135  				Type: DiffTypeEdited,
  1136  				Objects: []*ObjectDiff{
  1137  					{
  1138  						Type: DiffTypeDeleted,
  1139  						Name: "RestartPolicy",
  1140  						Fields: []*FieldDiff{
  1141  							{
  1142  								Type: DiffTypeDeleted,
  1143  								Name: "Attempts",
  1144  								Old:  "1",
  1145  								New:  "",
  1146  							},
  1147  							{
  1148  								Type: DiffTypeDeleted,
  1149  								Name: "Delay",
  1150  								Old:  "1000000000",
  1151  								New:  "",
  1152  							},
  1153  							{
  1154  								Type: DiffTypeDeleted,
  1155  								Name: "Interval",
  1156  								Old:  "1000000000",
  1157  								New:  "",
  1158  							},
  1159  							{
  1160  								Type: DiffTypeDeleted,
  1161  								Name: "Mode",
  1162  								Old:  "fail",
  1163  								New:  "",
  1164  							},
  1165  						},
  1166  					},
  1167  				},
  1168  			},
  1169  		},
  1170  		{
  1171  			// RestartPolicy edited
  1172  			Old: &TaskGroup{
  1173  				RestartPolicy: &RestartPolicy{
  1174  					Attempts: 1,
  1175  					Interval: 1 * time.Second,
  1176  					Delay:    1 * time.Second,
  1177  					Mode:     "fail",
  1178  				},
  1179  			},
  1180  			New: &TaskGroup{
  1181  				RestartPolicy: &RestartPolicy{
  1182  					Attempts: 2,
  1183  					Interval: 2 * time.Second,
  1184  					Delay:    2 * time.Second,
  1185  					Mode:     "delay",
  1186  				},
  1187  			},
  1188  			Expected: &TaskGroupDiff{
  1189  				Type: DiffTypeEdited,
  1190  				Objects: []*ObjectDiff{
  1191  					{
  1192  						Type: DiffTypeEdited,
  1193  						Name: "RestartPolicy",
  1194  						Fields: []*FieldDiff{
  1195  							{
  1196  								Type: DiffTypeEdited,
  1197  								Name: "Attempts",
  1198  								Old:  "1",
  1199  								New:  "2",
  1200  							},
  1201  							{
  1202  								Type: DiffTypeEdited,
  1203  								Name: "Delay",
  1204  								Old:  "1000000000",
  1205  								New:  "2000000000",
  1206  							},
  1207  							{
  1208  								Type: DiffTypeEdited,
  1209  								Name: "Interval",
  1210  								Old:  "1000000000",
  1211  								New:  "2000000000",
  1212  							},
  1213  							{
  1214  								Type: DiffTypeEdited,
  1215  								Name: "Mode",
  1216  								Old:  "fail",
  1217  								New:  "delay",
  1218  							},
  1219  						},
  1220  					},
  1221  				},
  1222  			},
  1223  		},
  1224  		{
  1225  			// RestartPolicy edited with context
  1226  			Contextual: true,
  1227  			Old: &TaskGroup{
  1228  				RestartPolicy: &RestartPolicy{
  1229  					Attempts: 1,
  1230  					Interval: 1 * time.Second,
  1231  					Delay:    1 * time.Second,
  1232  					Mode:     "fail",
  1233  				},
  1234  			},
  1235  			New: &TaskGroup{
  1236  				RestartPolicy: &RestartPolicy{
  1237  					Attempts: 2,
  1238  					Interval: 2 * time.Second,
  1239  					Delay:    1 * time.Second,
  1240  					Mode:     "fail",
  1241  				},
  1242  			},
  1243  			Expected: &TaskGroupDiff{
  1244  				Type: DiffTypeEdited,
  1245  				Objects: []*ObjectDiff{
  1246  					{
  1247  						Type: DiffTypeEdited,
  1248  						Name: "RestartPolicy",
  1249  						Fields: []*FieldDiff{
  1250  							{
  1251  								Type: DiffTypeEdited,
  1252  								Name: "Attempts",
  1253  								Old:  "1",
  1254  								New:  "2",
  1255  							},
  1256  							{
  1257  								Type: DiffTypeNone,
  1258  								Name: "Delay",
  1259  								Old:  "1000000000",
  1260  								New:  "1000000000",
  1261  							},
  1262  							{
  1263  								Type: DiffTypeEdited,
  1264  								Name: "Interval",
  1265  								Old:  "1000000000",
  1266  								New:  "2000000000",
  1267  							},
  1268  							{
  1269  								Type: DiffTypeNone,
  1270  								Name: "Mode",
  1271  								Old:  "fail",
  1272  								New:  "fail",
  1273  							},
  1274  						},
  1275  					},
  1276  				},
  1277  			},
  1278  		},
  1279  		{
  1280  			// Tasks edited
  1281  			Old: &TaskGroup{
  1282  				Tasks: []*Task{
  1283  					{
  1284  						Name:   "foo",
  1285  						Driver: "docker",
  1286  					},
  1287  					{
  1288  						Name:   "bar",
  1289  						Driver: "docker",
  1290  					},
  1291  					{
  1292  						Name:   "baz",
  1293  						Driver: "docker",
  1294  					},
  1295  				},
  1296  			},
  1297  			New: &TaskGroup{
  1298  				Tasks: []*Task{
  1299  					{
  1300  						Name:   "bar",
  1301  						Driver: "docker",
  1302  					},
  1303  					{
  1304  						Name:   "baz",
  1305  						Driver: "exec",
  1306  					},
  1307  					{
  1308  						Name:   "bam",
  1309  						Driver: "docker",
  1310  					},
  1311  				},
  1312  			},
  1313  			Expected: &TaskGroupDiff{
  1314  				Type: DiffTypeEdited,
  1315  				Tasks: []*TaskDiff{
  1316  					{
  1317  						Type: DiffTypeAdded,
  1318  						Name: "bam",
  1319  						Fields: []*FieldDiff{
  1320  							{
  1321  								Type: DiffTypeAdded,
  1322  								Name: "Driver",
  1323  								Old:  "",
  1324  								New:  "docker",
  1325  							},
  1326  							{
  1327  								Type: DiffTypeAdded,
  1328  								Name: "KillTimeout",
  1329  								Old:  "",
  1330  								New:  "0",
  1331  							},
  1332  						},
  1333  					},
  1334  					{
  1335  						Type: DiffTypeNone,
  1336  						Name: "bar",
  1337  					},
  1338  					{
  1339  						Type: DiffTypeEdited,
  1340  						Name: "baz",
  1341  						Fields: []*FieldDiff{
  1342  							{
  1343  								Type: DiffTypeEdited,
  1344  								Name: "Driver",
  1345  								Old:  "docker",
  1346  								New:  "exec",
  1347  							},
  1348  						},
  1349  					},
  1350  					{
  1351  						Type: DiffTypeDeleted,
  1352  						Name: "foo",
  1353  						Fields: []*FieldDiff{
  1354  							{
  1355  								Type: DiffTypeDeleted,
  1356  								Name: "Driver",
  1357  								Old:  "docker",
  1358  								New:  "",
  1359  							},
  1360  							{
  1361  								Type: DiffTypeDeleted,
  1362  								Name: "KillTimeout",
  1363  								Old:  "0",
  1364  								New:  "",
  1365  							},
  1366  						},
  1367  					},
  1368  				},
  1369  			},
  1370  		},
  1371  	}
  1372  
  1373  	for i, c := range cases {
  1374  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1375  		if c.Error && err == nil {
  1376  			t.Fatalf("case %d: expected errored")
  1377  		} else if err != nil {
  1378  			if !c.Error {
  1379  				t.Fatalf("case %d: errored %#v", i+1, err)
  1380  			} else {
  1381  				continue
  1382  			}
  1383  		}
  1384  
  1385  		if !reflect.DeepEqual(actual, c.Expected) {
  1386  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1387  				i+1, actual, c.Expected)
  1388  		}
  1389  	}
  1390  }
  1391  
  1392  func TestTaskDiff(t *testing.T) {
  1393  	cases := []struct {
  1394  		Old, New   *Task
  1395  		Expected   *TaskDiff
  1396  		Error      bool
  1397  		Contextual bool
  1398  	}{
  1399  		{
  1400  			Old: nil,
  1401  			New: nil,
  1402  			Expected: &TaskDiff{
  1403  				Type: DiffTypeNone,
  1404  			},
  1405  		},
  1406  		{
  1407  			// Primitive only that has different names
  1408  			Old: &Task{
  1409  				Name: "foo",
  1410  				Meta: map[string]string{
  1411  					"foo": "bar",
  1412  				},
  1413  			},
  1414  			New: &Task{
  1415  				Name: "bar",
  1416  				Meta: map[string]string{
  1417  					"foo": "bar",
  1418  				},
  1419  			},
  1420  			Error: true,
  1421  		},
  1422  		{
  1423  			// Primitive only that is the same
  1424  			Old: &Task{
  1425  				Name:   "foo",
  1426  				Driver: "exec",
  1427  				User:   "foo",
  1428  				Env: map[string]string{
  1429  					"FOO": "bar",
  1430  				},
  1431  				Meta: map[string]string{
  1432  					"foo": "bar",
  1433  				},
  1434  				KillTimeout: 1 * time.Second,
  1435  			},
  1436  			New: &Task{
  1437  				Name:   "foo",
  1438  				Driver: "exec",
  1439  				User:   "foo",
  1440  				Env: map[string]string{
  1441  					"FOO": "bar",
  1442  				},
  1443  				Meta: map[string]string{
  1444  					"foo": "bar",
  1445  				},
  1446  				KillTimeout: 1 * time.Second,
  1447  			},
  1448  			Expected: &TaskDiff{
  1449  				Type: DiffTypeNone,
  1450  				Name: "foo",
  1451  			},
  1452  		},
  1453  		{
  1454  			// Primitive only that has diffs
  1455  			Old: &Task{
  1456  				Name:   "foo",
  1457  				Driver: "exec",
  1458  				User:   "foo",
  1459  				Env: map[string]string{
  1460  					"FOO": "bar",
  1461  				},
  1462  				Meta: map[string]string{
  1463  					"foo": "bar",
  1464  				},
  1465  				KillTimeout: 1 * time.Second,
  1466  			},
  1467  			New: &Task{
  1468  				Name:   "foo",
  1469  				Driver: "docker",
  1470  				User:   "bar",
  1471  				Env: map[string]string{
  1472  					"FOO": "baz",
  1473  				},
  1474  				Meta: map[string]string{
  1475  					"foo": "baz",
  1476  				},
  1477  				KillTimeout: 2 * time.Second,
  1478  			},
  1479  			Expected: &TaskDiff{
  1480  				Type: DiffTypeEdited,
  1481  				Name: "foo",
  1482  				Fields: []*FieldDiff{
  1483  					{
  1484  						Type: DiffTypeEdited,
  1485  						Name: "Driver",
  1486  						Old:  "exec",
  1487  						New:  "docker",
  1488  					},
  1489  					{
  1490  						Type: DiffTypeEdited,
  1491  						Name: "Env[FOO]",
  1492  						Old:  "bar",
  1493  						New:  "baz",
  1494  					},
  1495  					{
  1496  						Type: DiffTypeEdited,
  1497  						Name: "KillTimeout",
  1498  						Old:  "1000000000",
  1499  						New:  "2000000000",
  1500  					},
  1501  					{
  1502  						Type: DiffTypeEdited,
  1503  						Name: "Meta[foo]",
  1504  						Old:  "bar",
  1505  						New:  "baz",
  1506  					},
  1507  					{
  1508  						Type: DiffTypeEdited,
  1509  						Name: "User",
  1510  						Old:  "foo",
  1511  						New:  "bar",
  1512  					},
  1513  				},
  1514  			},
  1515  		},
  1516  		{
  1517  			// Map diff
  1518  			Old: &Task{
  1519  				Meta: map[string]string{
  1520  					"foo": "foo",
  1521  					"bar": "bar",
  1522  				},
  1523  				Env: map[string]string{
  1524  					"foo": "foo",
  1525  					"bar": "bar",
  1526  				},
  1527  			},
  1528  			New: &Task{
  1529  				Meta: map[string]string{
  1530  					"bar": "bar",
  1531  					"baz": "baz",
  1532  				},
  1533  				Env: map[string]string{
  1534  					"bar": "bar",
  1535  					"baz": "baz",
  1536  				},
  1537  			},
  1538  			Expected: &TaskDiff{
  1539  				Type: DiffTypeEdited,
  1540  				Fields: []*FieldDiff{
  1541  					{
  1542  						Type: DiffTypeAdded,
  1543  						Name: "Env[baz]",
  1544  						Old:  "",
  1545  						New:  "baz",
  1546  					},
  1547  					{
  1548  						Type: DiffTypeDeleted,
  1549  						Name: "Env[foo]",
  1550  						Old:  "foo",
  1551  						New:  "",
  1552  					},
  1553  					{
  1554  						Type: DiffTypeAdded,
  1555  						Name: "Meta[baz]",
  1556  						Old:  "",
  1557  						New:  "baz",
  1558  					},
  1559  					{
  1560  						Type: DiffTypeDeleted,
  1561  						Name: "Meta[foo]",
  1562  						Old:  "foo",
  1563  						New:  "",
  1564  					},
  1565  				},
  1566  			},
  1567  		},
  1568  		{
  1569  			// Constraints edited
  1570  			Old: &Task{
  1571  				Constraints: []*Constraint{
  1572  					{
  1573  						LTarget: "foo",
  1574  						RTarget: "foo",
  1575  						Operand: "foo",
  1576  						str:     "foo",
  1577  					},
  1578  					{
  1579  						LTarget: "bar",
  1580  						RTarget: "bar",
  1581  						Operand: "bar",
  1582  						str:     "bar",
  1583  					},
  1584  				},
  1585  			},
  1586  			New: &Task{
  1587  				Constraints: []*Constraint{
  1588  					{
  1589  						LTarget: "foo",
  1590  						RTarget: "foo",
  1591  						Operand: "foo",
  1592  						str:     "foo",
  1593  					},
  1594  					{
  1595  						LTarget: "baz",
  1596  						RTarget: "baz",
  1597  						Operand: "baz",
  1598  						str:     "baz",
  1599  					},
  1600  				},
  1601  			},
  1602  			Expected: &TaskDiff{
  1603  				Type: DiffTypeEdited,
  1604  				Objects: []*ObjectDiff{
  1605  					{
  1606  						Type: DiffTypeAdded,
  1607  						Name: "Constraint",
  1608  						Fields: []*FieldDiff{
  1609  							{
  1610  								Type: DiffTypeAdded,
  1611  								Name: "LTarget",
  1612  								Old:  "",
  1613  								New:  "baz",
  1614  							},
  1615  							{
  1616  								Type: DiffTypeAdded,
  1617  								Name: "Operand",
  1618  								Old:  "",
  1619  								New:  "baz",
  1620  							},
  1621  							{
  1622  								Type: DiffTypeAdded,
  1623  								Name: "RTarget",
  1624  								Old:  "",
  1625  								New:  "baz",
  1626  							},
  1627  						},
  1628  					},
  1629  					{
  1630  						Type: DiffTypeDeleted,
  1631  						Name: "Constraint",
  1632  						Fields: []*FieldDiff{
  1633  							{
  1634  								Type: DiffTypeDeleted,
  1635  								Name: "LTarget",
  1636  								Old:  "bar",
  1637  								New:  "",
  1638  							},
  1639  							{
  1640  								Type: DiffTypeDeleted,
  1641  								Name: "Operand",
  1642  								Old:  "bar",
  1643  								New:  "",
  1644  							},
  1645  							{
  1646  								Type: DiffTypeDeleted,
  1647  								Name: "RTarget",
  1648  								Old:  "bar",
  1649  								New:  "",
  1650  							},
  1651  						},
  1652  					},
  1653  				},
  1654  			},
  1655  		},
  1656  		{
  1657  			// LogConfig added
  1658  			Old: &Task{},
  1659  			New: &Task{
  1660  				LogConfig: &LogConfig{
  1661  					MaxFiles:      1,
  1662  					MaxFileSizeMB: 10,
  1663  				},
  1664  			},
  1665  			Expected: &TaskDiff{
  1666  				Type: DiffTypeEdited,
  1667  				Objects: []*ObjectDiff{
  1668  					{
  1669  						Type: DiffTypeAdded,
  1670  						Name: "LogConfig",
  1671  						Fields: []*FieldDiff{
  1672  							{
  1673  								Type: DiffTypeAdded,
  1674  								Name: "MaxFileSizeMB",
  1675  								Old:  "",
  1676  								New:  "10",
  1677  							},
  1678  							{
  1679  								Type: DiffTypeAdded,
  1680  								Name: "MaxFiles",
  1681  								Old:  "",
  1682  								New:  "1",
  1683  							},
  1684  						},
  1685  					},
  1686  				},
  1687  			},
  1688  		},
  1689  		{
  1690  			// LogConfig deleted
  1691  			Old: &Task{
  1692  				LogConfig: &LogConfig{
  1693  					MaxFiles:      1,
  1694  					MaxFileSizeMB: 10,
  1695  				},
  1696  			},
  1697  			New: &Task{},
  1698  			Expected: &TaskDiff{
  1699  				Type: DiffTypeEdited,
  1700  				Objects: []*ObjectDiff{
  1701  					{
  1702  						Type: DiffTypeDeleted,
  1703  						Name: "LogConfig",
  1704  						Fields: []*FieldDiff{
  1705  							{
  1706  								Type: DiffTypeDeleted,
  1707  								Name: "MaxFileSizeMB",
  1708  								Old:  "10",
  1709  								New:  "",
  1710  							},
  1711  							{
  1712  								Type: DiffTypeDeleted,
  1713  								Name: "MaxFiles",
  1714  								Old:  "1",
  1715  								New:  "",
  1716  							},
  1717  						},
  1718  					},
  1719  				},
  1720  			},
  1721  		},
  1722  		{
  1723  			// LogConfig edited
  1724  			Old: &Task{
  1725  				LogConfig: &LogConfig{
  1726  					MaxFiles:      1,
  1727  					MaxFileSizeMB: 10,
  1728  				},
  1729  			},
  1730  			New: &Task{
  1731  				LogConfig: &LogConfig{
  1732  					MaxFiles:      2,
  1733  					MaxFileSizeMB: 20,
  1734  				},
  1735  			},
  1736  			Expected: &TaskDiff{
  1737  				Type: DiffTypeEdited,
  1738  				Objects: []*ObjectDiff{
  1739  					{
  1740  						Type: DiffTypeEdited,
  1741  						Name: "LogConfig",
  1742  						Fields: []*FieldDiff{
  1743  							{
  1744  								Type: DiffTypeEdited,
  1745  								Name: "MaxFileSizeMB",
  1746  								Old:  "10",
  1747  								New:  "20",
  1748  							},
  1749  							{
  1750  								Type: DiffTypeEdited,
  1751  								Name: "MaxFiles",
  1752  								Old:  "1",
  1753  								New:  "2",
  1754  							},
  1755  						},
  1756  					},
  1757  				},
  1758  			},
  1759  		},
  1760  		{
  1761  			// LogConfig edited with context
  1762  			Contextual: true,
  1763  			Old: &Task{
  1764  				LogConfig: &LogConfig{
  1765  					MaxFiles:      1,
  1766  					MaxFileSizeMB: 10,
  1767  				},
  1768  			},
  1769  			New: &Task{
  1770  				LogConfig: &LogConfig{
  1771  					MaxFiles:      1,
  1772  					MaxFileSizeMB: 20,
  1773  				},
  1774  			},
  1775  			Expected: &TaskDiff{
  1776  				Type: DiffTypeEdited,
  1777  				Objects: []*ObjectDiff{
  1778  					{
  1779  						Type: DiffTypeEdited,
  1780  						Name: "LogConfig",
  1781  						Fields: []*FieldDiff{
  1782  							{
  1783  								Type: DiffTypeEdited,
  1784  								Name: "MaxFileSizeMB",
  1785  								Old:  "10",
  1786  								New:  "20",
  1787  							},
  1788  							{
  1789  								Type: DiffTypeNone,
  1790  								Name: "MaxFiles",
  1791  								Old:  "1",
  1792  								New:  "1",
  1793  							},
  1794  						},
  1795  					},
  1796  				},
  1797  			},
  1798  		},
  1799  		{
  1800  			// Artifacts edited
  1801  			Old: &Task{
  1802  				Artifacts: []*TaskArtifact{
  1803  					{
  1804  						GetterSource: "foo",
  1805  						GetterOptions: map[string]string{
  1806  							"foo": "bar",
  1807  						},
  1808  						RelativeDest: "foo",
  1809  					},
  1810  					{
  1811  						GetterSource: "bar",
  1812  						GetterOptions: map[string]string{
  1813  							"bar": "baz",
  1814  						},
  1815  						RelativeDest: "bar",
  1816  					},
  1817  				},
  1818  			},
  1819  			New: &Task{
  1820  				Artifacts: []*TaskArtifact{
  1821  					{
  1822  						GetterSource: "foo",
  1823  						GetterOptions: map[string]string{
  1824  							"foo": "bar",
  1825  						},
  1826  						RelativeDest: "foo",
  1827  					},
  1828  					{
  1829  						GetterSource: "bam",
  1830  						GetterOptions: map[string]string{
  1831  							"bam": "baz",
  1832  						},
  1833  						RelativeDest: "bam",
  1834  					},
  1835  				},
  1836  			},
  1837  			Expected: &TaskDiff{
  1838  				Type: DiffTypeEdited,
  1839  				Objects: []*ObjectDiff{
  1840  					{
  1841  						Type: DiffTypeAdded,
  1842  						Name: "Artifact",
  1843  						Fields: []*FieldDiff{
  1844  							{
  1845  								Type: DiffTypeAdded,
  1846  								Name: "GetterOptions[bam]",
  1847  								Old:  "",
  1848  								New:  "baz",
  1849  							},
  1850  							{
  1851  								Type: DiffTypeAdded,
  1852  								Name: "GetterSource",
  1853  								Old:  "",
  1854  								New:  "bam",
  1855  							},
  1856  							{
  1857  								Type: DiffTypeAdded,
  1858  								Name: "RelativeDest",
  1859  								Old:  "",
  1860  								New:  "bam",
  1861  							},
  1862  						},
  1863  					},
  1864  					{
  1865  						Type: DiffTypeDeleted,
  1866  						Name: "Artifact",
  1867  						Fields: []*FieldDiff{
  1868  							{
  1869  								Type: DiffTypeDeleted,
  1870  								Name: "GetterOptions[bar]",
  1871  								Old:  "baz",
  1872  								New:  "",
  1873  							},
  1874  							{
  1875  								Type: DiffTypeDeleted,
  1876  								Name: "GetterSource",
  1877  								Old:  "bar",
  1878  								New:  "",
  1879  							},
  1880  							{
  1881  								Type: DiffTypeDeleted,
  1882  								Name: "RelativeDest",
  1883  								Old:  "bar",
  1884  								New:  "",
  1885  							},
  1886  						},
  1887  					},
  1888  				},
  1889  			},
  1890  		},
  1891  		{
  1892  			// Resources edited (no networks)
  1893  			Old: &Task{
  1894  				Resources: &Resources{
  1895  					CPU:      100,
  1896  					MemoryMB: 100,
  1897  					DiskMB:   100,
  1898  					IOPS:     100,
  1899  				},
  1900  			},
  1901  			New: &Task{
  1902  				Resources: &Resources{
  1903  					CPU:      200,
  1904  					MemoryMB: 200,
  1905  					DiskMB:   200,
  1906  					IOPS:     200,
  1907  				},
  1908  			},
  1909  			Expected: &TaskDiff{
  1910  				Type: DiffTypeEdited,
  1911  				Objects: []*ObjectDiff{
  1912  					{
  1913  						Type: DiffTypeEdited,
  1914  						Name: "Resources",
  1915  						Fields: []*FieldDiff{
  1916  							{
  1917  								Type: DiffTypeEdited,
  1918  								Name: "CPU",
  1919  								Old:  "100",
  1920  								New:  "200",
  1921  							},
  1922  							{
  1923  								Type: DiffTypeEdited,
  1924  								Name: "DiskMB",
  1925  								Old:  "100",
  1926  								New:  "200",
  1927  							},
  1928  							{
  1929  								Type: DiffTypeEdited,
  1930  								Name: "IOPS",
  1931  								Old:  "100",
  1932  								New:  "200",
  1933  							},
  1934  							{
  1935  								Type: DiffTypeEdited,
  1936  								Name: "MemoryMB",
  1937  								Old:  "100",
  1938  								New:  "200",
  1939  							},
  1940  						},
  1941  					},
  1942  				},
  1943  			},
  1944  		},
  1945  		{
  1946  			// Resources edited (no networks) with context
  1947  			Contextual: true,
  1948  			Old: &Task{
  1949  				Resources: &Resources{
  1950  					CPU:      100,
  1951  					MemoryMB: 100,
  1952  					DiskMB:   100,
  1953  					IOPS:     100,
  1954  				},
  1955  			},
  1956  			New: &Task{
  1957  				Resources: &Resources{
  1958  					CPU:      200,
  1959  					MemoryMB: 100,
  1960  					DiskMB:   200,
  1961  					IOPS:     100,
  1962  				},
  1963  			},
  1964  			Expected: &TaskDiff{
  1965  				Type: DiffTypeEdited,
  1966  				Objects: []*ObjectDiff{
  1967  					{
  1968  						Type: DiffTypeEdited,
  1969  						Name: "Resources",
  1970  						Fields: []*FieldDiff{
  1971  							{
  1972  								Type: DiffTypeEdited,
  1973  								Name: "CPU",
  1974  								Old:  "100",
  1975  								New:  "200",
  1976  							},
  1977  							{
  1978  								Type: DiffTypeEdited,
  1979  								Name: "DiskMB",
  1980  								Old:  "100",
  1981  								New:  "200",
  1982  							},
  1983  							{
  1984  								Type: DiffTypeNone,
  1985  								Name: "IOPS",
  1986  								Old:  "100",
  1987  								New:  "100",
  1988  							},
  1989  							{
  1990  								Type: DiffTypeNone,
  1991  								Name: "MemoryMB",
  1992  								Old:  "100",
  1993  								New:  "100",
  1994  							},
  1995  						},
  1996  					},
  1997  				},
  1998  			},
  1999  		},
  2000  		{
  2001  			// Network Resources edited
  2002  			Old: &Task{
  2003  				Resources: &Resources{
  2004  					Networks: []*NetworkResource{
  2005  						{
  2006  							Device: "foo",
  2007  							CIDR:   "foo",
  2008  							IP:     "foo",
  2009  							MBits:  100,
  2010  							ReservedPorts: []Port{
  2011  								{
  2012  									Label: "foo",
  2013  									Value: 80,
  2014  								},
  2015  							},
  2016  							DynamicPorts: []Port{
  2017  								{
  2018  									Label: "bar",
  2019  								},
  2020  							},
  2021  						},
  2022  					},
  2023  				},
  2024  			},
  2025  			New: &Task{
  2026  				Resources: &Resources{
  2027  					Networks: []*NetworkResource{
  2028  						{
  2029  							Device: "bar",
  2030  							CIDR:   "bar",
  2031  							IP:     "bar",
  2032  							MBits:  200,
  2033  							ReservedPorts: []Port{
  2034  								{
  2035  									Label: "foo",
  2036  									Value: 81,
  2037  								},
  2038  							},
  2039  							DynamicPorts: []Port{
  2040  								{
  2041  									Label: "baz",
  2042  								},
  2043  							},
  2044  						},
  2045  					},
  2046  				},
  2047  			},
  2048  			Expected: &TaskDiff{
  2049  				Type: DiffTypeEdited,
  2050  				Objects: []*ObjectDiff{
  2051  					{
  2052  						Type: DiffTypeEdited,
  2053  						Name: "Resources",
  2054  						Objects: []*ObjectDiff{
  2055  							{
  2056  								Type: DiffTypeAdded,
  2057  								Name: "Network",
  2058  								Fields: []*FieldDiff{
  2059  									{
  2060  										Type: DiffTypeAdded,
  2061  										Name: "MBits",
  2062  										Old:  "",
  2063  										New:  "200",
  2064  									},
  2065  								},
  2066  								Objects: []*ObjectDiff{
  2067  									{
  2068  										Type: DiffTypeAdded,
  2069  										Name: "Static Port",
  2070  										Fields: []*FieldDiff{
  2071  											{
  2072  												Type: DiffTypeAdded,
  2073  												Name: "Label",
  2074  												Old:  "",
  2075  												New:  "foo",
  2076  											},
  2077  											{
  2078  												Type: DiffTypeAdded,
  2079  												Name: "Value",
  2080  												Old:  "",
  2081  												New:  "81",
  2082  											},
  2083  										},
  2084  									},
  2085  									{
  2086  										Type: DiffTypeAdded,
  2087  										Name: "Dynamic Port",
  2088  										Fields: []*FieldDiff{
  2089  											{
  2090  												Type: DiffTypeAdded,
  2091  												Name: "Label",
  2092  												Old:  "",
  2093  												New:  "baz",
  2094  											},
  2095  										},
  2096  									},
  2097  								},
  2098  							},
  2099  							{
  2100  								Type: DiffTypeDeleted,
  2101  								Name: "Network",
  2102  								Fields: []*FieldDiff{
  2103  									{
  2104  										Type: DiffTypeDeleted,
  2105  										Name: "MBits",
  2106  										Old:  "100",
  2107  										New:  "",
  2108  									},
  2109  								},
  2110  								Objects: []*ObjectDiff{
  2111  									{
  2112  										Type: DiffTypeDeleted,
  2113  										Name: "Static Port",
  2114  										Fields: []*FieldDiff{
  2115  											{
  2116  												Type: DiffTypeDeleted,
  2117  												Name: "Label",
  2118  												Old:  "foo",
  2119  												New:  "",
  2120  											},
  2121  											{
  2122  												Type: DiffTypeDeleted,
  2123  												Name: "Value",
  2124  												Old:  "80",
  2125  												New:  "",
  2126  											},
  2127  										},
  2128  									},
  2129  									{
  2130  										Type: DiffTypeDeleted,
  2131  										Name: "Dynamic Port",
  2132  										Fields: []*FieldDiff{
  2133  											{
  2134  												Type: DiffTypeDeleted,
  2135  												Name: "Label",
  2136  												Old:  "bar",
  2137  												New:  "",
  2138  											},
  2139  										},
  2140  									},
  2141  								},
  2142  							},
  2143  						},
  2144  					},
  2145  				},
  2146  			},
  2147  		},
  2148  		{
  2149  			// Config same
  2150  			Old: &Task{
  2151  				Config: map[string]interface{}{
  2152  					"foo": 1,
  2153  					"bar": "bar",
  2154  					"bam": []string{"a", "b"},
  2155  					"baz": map[string]int{
  2156  						"a": 1,
  2157  						"b": 2,
  2158  					},
  2159  					"boom": &Port{
  2160  						Label: "boom_port",
  2161  					},
  2162  				},
  2163  			},
  2164  			New: &Task{
  2165  				Config: map[string]interface{}{
  2166  					"foo": 1,
  2167  					"bar": "bar",
  2168  					"bam": []string{"a", "b"},
  2169  					"baz": map[string]int{
  2170  						"a": 1,
  2171  						"b": 2,
  2172  					},
  2173  					"boom": &Port{
  2174  						Label: "boom_port",
  2175  					},
  2176  				},
  2177  			},
  2178  			Expected: &TaskDiff{
  2179  				Type: DiffTypeNone,
  2180  			},
  2181  		},
  2182  		{
  2183  			// Config edited
  2184  			Old: &Task{
  2185  				Config: map[string]interface{}{
  2186  					"foo": 1,
  2187  					"bar": "baz",
  2188  					"bam": []string{"a", "b"},
  2189  					"baz": map[string]int{
  2190  						"a": 1,
  2191  						"b": 2,
  2192  					},
  2193  					"boom": &Port{
  2194  						Label: "boom_port",
  2195  					},
  2196  				},
  2197  			},
  2198  			New: &Task{
  2199  				Config: map[string]interface{}{
  2200  					"foo": 2,
  2201  					"bar": "baz",
  2202  					"bam": []string{"a", "c", "d"},
  2203  					"baz": map[string]int{
  2204  						"b": 3,
  2205  						"c": 4,
  2206  					},
  2207  					"boom": &Port{
  2208  						Label: "boom_port2",
  2209  					},
  2210  				},
  2211  			},
  2212  			Expected: &TaskDiff{
  2213  				Type: DiffTypeEdited,
  2214  				Objects: []*ObjectDiff{
  2215  					{
  2216  						Type: DiffTypeEdited,
  2217  						Name: "Config",
  2218  						Fields: []*FieldDiff{
  2219  							{
  2220  								Type: DiffTypeEdited,
  2221  								Name: "bam[1]",
  2222  								Old:  "b",
  2223  								New:  "c",
  2224  							},
  2225  							{
  2226  								Type: DiffTypeAdded,
  2227  								Name: "bam[2]",
  2228  								Old:  "",
  2229  								New:  "d",
  2230  							},
  2231  							{
  2232  								Type: DiffTypeDeleted,
  2233  								Name: "baz[a]",
  2234  								Old:  "1",
  2235  								New:  "",
  2236  							},
  2237  							{
  2238  								Type: DiffTypeEdited,
  2239  								Name: "baz[b]",
  2240  								Old:  "2",
  2241  								New:  "3",
  2242  							},
  2243  							{
  2244  								Type: DiffTypeAdded,
  2245  								Name: "baz[c]",
  2246  								Old:  "",
  2247  								New:  "4",
  2248  							},
  2249  							{
  2250  								Type: DiffTypeEdited,
  2251  								Name: "boom.Label",
  2252  								Old:  "boom_port",
  2253  								New:  "boom_port2",
  2254  							},
  2255  							{
  2256  								Type: DiffTypeEdited,
  2257  								Name: "foo",
  2258  								Old:  "1",
  2259  								New:  "2",
  2260  							},
  2261  						},
  2262  					},
  2263  				},
  2264  			},
  2265  		},
  2266  		{
  2267  			// Config edited with context
  2268  			Contextual: true,
  2269  			Old: &Task{
  2270  				Config: map[string]interface{}{
  2271  					"foo": 1,
  2272  					"bar": "baz",
  2273  					"bam": []string{"a", "b"},
  2274  					"baz": map[string]int{
  2275  						"a": 1,
  2276  						"b": 2,
  2277  					},
  2278  					"boom": &Port{
  2279  						Label: "boom_port",
  2280  					},
  2281  				},
  2282  			},
  2283  			New: &Task{
  2284  				Config: map[string]interface{}{
  2285  					"foo": 2,
  2286  					"bar": "baz",
  2287  					"bam": []string{"a", "c", "d"},
  2288  					"baz": map[string]int{
  2289  						"a": 1,
  2290  						"b": 2,
  2291  					},
  2292  					"boom": &Port{
  2293  						Label: "boom_port",
  2294  					},
  2295  				},
  2296  			},
  2297  			Expected: &TaskDiff{
  2298  				Type: DiffTypeEdited,
  2299  				Objects: []*ObjectDiff{
  2300  					{
  2301  						Type: DiffTypeEdited,
  2302  						Name: "Config",
  2303  						Fields: []*FieldDiff{
  2304  							{
  2305  								Type: DiffTypeNone,
  2306  								Name: "bam[0]",
  2307  								Old:  "a",
  2308  								New:  "a",
  2309  							},
  2310  							{
  2311  								Type: DiffTypeEdited,
  2312  								Name: "bam[1]",
  2313  								Old:  "b",
  2314  								New:  "c",
  2315  							},
  2316  							{
  2317  								Type: DiffTypeAdded,
  2318  								Name: "bam[2]",
  2319  								Old:  "",
  2320  								New:  "d",
  2321  							},
  2322  							{
  2323  								Type: DiffTypeNone,
  2324  								Name: "bar",
  2325  								Old:  "baz",
  2326  								New:  "baz",
  2327  							},
  2328  							{
  2329  								Type: DiffTypeNone,
  2330  								Name: "baz[a]",
  2331  								Old:  "1",
  2332  								New:  "1",
  2333  							},
  2334  							{
  2335  								Type: DiffTypeNone,
  2336  								Name: "baz[b]",
  2337  								Old:  "2",
  2338  								New:  "2",
  2339  							},
  2340  							{
  2341  								Type: DiffTypeNone,
  2342  								Name: "boom.Label",
  2343  								Old:  "boom_port",
  2344  								New:  "boom_port",
  2345  							},
  2346  							{
  2347  								Type: DiffTypeNone,
  2348  								Name: "boom.Value",
  2349  								Old:  "0",
  2350  								New:  "0",
  2351  							},
  2352  							{
  2353  								Type: DiffTypeEdited,
  2354  								Name: "foo",
  2355  								Old:  "1",
  2356  								New:  "2",
  2357  							},
  2358  						},
  2359  					},
  2360  				},
  2361  			},
  2362  		},
  2363  		{
  2364  			// Services edited (no checks)
  2365  			Old: &Task{
  2366  				Services: []*Service{
  2367  					{
  2368  						Name:      "foo",
  2369  						PortLabel: "foo",
  2370  					},
  2371  					{
  2372  						Name:      "bar",
  2373  						PortLabel: "bar",
  2374  					},
  2375  					{
  2376  						Name:      "baz",
  2377  						PortLabel: "baz",
  2378  					},
  2379  				},
  2380  			},
  2381  			New: &Task{
  2382  				Services: []*Service{
  2383  					{
  2384  						Name:      "bar",
  2385  						PortLabel: "bar",
  2386  					},
  2387  					{
  2388  						Name:      "baz",
  2389  						PortLabel: "baz2",
  2390  					},
  2391  					{
  2392  						Name:      "bam",
  2393  						PortLabel: "bam",
  2394  					},
  2395  				},
  2396  			},
  2397  			Expected: &TaskDiff{
  2398  				Type: DiffTypeEdited,
  2399  				Objects: []*ObjectDiff{
  2400  					{
  2401  						Type: DiffTypeEdited,
  2402  						Name: "Service",
  2403  						Fields: []*FieldDiff{
  2404  							{
  2405  								Type: DiffTypeEdited,
  2406  								Name: "PortLabel",
  2407  								Old:  "baz",
  2408  								New:  "baz2",
  2409  							},
  2410  						},
  2411  					},
  2412  					{
  2413  						Type: DiffTypeAdded,
  2414  						Name: "Service",
  2415  						Fields: []*FieldDiff{
  2416  							{
  2417  								Type: DiffTypeAdded,
  2418  								Name: "Name",
  2419  								Old:  "",
  2420  								New:  "bam",
  2421  							},
  2422  							{
  2423  								Type: DiffTypeAdded,
  2424  								Name: "PortLabel",
  2425  								Old:  "",
  2426  								New:  "bam",
  2427  							},
  2428  						},
  2429  					},
  2430  					{
  2431  						Type: DiffTypeDeleted,
  2432  						Name: "Service",
  2433  						Fields: []*FieldDiff{
  2434  							{
  2435  								Type: DiffTypeDeleted,
  2436  								Name: "Name",
  2437  								Old:  "foo",
  2438  								New:  "",
  2439  							},
  2440  							{
  2441  								Type: DiffTypeDeleted,
  2442  								Name: "PortLabel",
  2443  								Old:  "foo",
  2444  								New:  "",
  2445  							},
  2446  						},
  2447  					},
  2448  				},
  2449  			},
  2450  		},
  2451  		{
  2452  			// Services edited (no checks) with context
  2453  			Contextual: true,
  2454  			Old: &Task{
  2455  				Services: []*Service{
  2456  					{
  2457  						Name:      "foo",
  2458  						PortLabel: "foo",
  2459  					},
  2460  				},
  2461  			},
  2462  			New: &Task{
  2463  				Services: []*Service{
  2464  					{
  2465  						Name:      "foo",
  2466  						PortLabel: "bar",
  2467  					},
  2468  				},
  2469  			},
  2470  			Expected: &TaskDiff{
  2471  				Type: DiffTypeEdited,
  2472  				Objects: []*ObjectDiff{
  2473  					{
  2474  						Type: DiffTypeEdited,
  2475  						Name: "Service",
  2476  						Fields: []*FieldDiff{
  2477  							{
  2478  								Type: DiffTypeNone,
  2479  								Name: "Name",
  2480  								Old:  "foo",
  2481  								New:  "foo",
  2482  							},
  2483  							{
  2484  								Type: DiffTypeEdited,
  2485  								Name: "PortLabel",
  2486  								Old:  "foo",
  2487  								New:  "bar",
  2488  							},
  2489  						},
  2490  					},
  2491  				},
  2492  			},
  2493  		},
  2494  		{
  2495  			// Service Checks edited
  2496  			Old: &Task{
  2497  				Services: []*Service{
  2498  					{
  2499  						Name: "foo",
  2500  						Checks: []*ServiceCheck{
  2501  							{
  2502  								Name:     "foo",
  2503  								Type:     "http",
  2504  								Command:  "foo",
  2505  								Args:     []string{"foo"},
  2506  								Path:     "foo",
  2507  								Protocol: "http",
  2508  								Interval: 1 * time.Second,
  2509  								Timeout:  1 * time.Second,
  2510  							},
  2511  							{
  2512  								Name:     "bar",
  2513  								Type:     "http",
  2514  								Command:  "foo",
  2515  								Args:     []string{"foo"},
  2516  								Path:     "foo",
  2517  								Protocol: "http",
  2518  								Interval: 1 * time.Second,
  2519  								Timeout:  1 * time.Second,
  2520  							},
  2521  							{
  2522  								Name:     "baz",
  2523  								Type:     "http",
  2524  								Command:  "foo",
  2525  								Args:     []string{"foo"},
  2526  								Path:     "foo",
  2527  								Protocol: "http",
  2528  								Interval: 1 * time.Second,
  2529  								Timeout:  1 * time.Second,
  2530  							},
  2531  						},
  2532  					},
  2533  				},
  2534  			},
  2535  			New: &Task{
  2536  				Services: []*Service{
  2537  					{
  2538  						Name: "foo",
  2539  						Checks: []*ServiceCheck{
  2540  							{
  2541  								Name:     "bar",
  2542  								Type:     "http",
  2543  								Command:  "foo",
  2544  								Args:     []string{"foo"},
  2545  								Path:     "foo",
  2546  								Protocol: "http",
  2547  								Interval: 1 * time.Second,
  2548  								Timeout:  1 * time.Second,
  2549  							},
  2550  							{
  2551  								Name:     "baz",
  2552  								Type:     "tcp",
  2553  								Command:  "foo",
  2554  								Args:     []string{"foo"},
  2555  								Path:     "foo",
  2556  								Protocol: "http",
  2557  								Interval: 1 * time.Second,
  2558  								Timeout:  1 * time.Second,
  2559  							},
  2560  							{
  2561  								Name:     "bam",
  2562  								Type:     "http",
  2563  								Command:  "foo",
  2564  								Args:     []string{"foo"},
  2565  								Path:     "foo",
  2566  								Protocol: "http",
  2567  								Interval: 1 * time.Second,
  2568  								Timeout:  1 * time.Second,
  2569  							},
  2570  						},
  2571  					},
  2572  				},
  2573  			},
  2574  			Expected: &TaskDiff{
  2575  				Type: DiffTypeEdited,
  2576  				Objects: []*ObjectDiff{
  2577  					{
  2578  						Type: DiffTypeEdited,
  2579  						Name: "Service",
  2580  						Objects: []*ObjectDiff{
  2581  							{
  2582  								Type: DiffTypeEdited,
  2583  								Name: "Check",
  2584  								Fields: []*FieldDiff{
  2585  									{
  2586  										Type: DiffTypeEdited,
  2587  										Name: "Type",
  2588  										Old:  "http",
  2589  										New:  "tcp",
  2590  									},
  2591  								},
  2592  							},
  2593  							{
  2594  								Type: DiffTypeAdded,
  2595  								Name: "Check",
  2596  								Fields: []*FieldDiff{
  2597  									{
  2598  										Type: DiffTypeAdded,
  2599  										Name: "Command",
  2600  										Old:  "",
  2601  										New:  "foo",
  2602  									},
  2603  									{
  2604  										Type: DiffTypeAdded,
  2605  										Name: "Interval",
  2606  										Old:  "",
  2607  										New:  "1000000000",
  2608  									},
  2609  									{
  2610  										Type: DiffTypeAdded,
  2611  										Name: "Name",
  2612  										Old:  "",
  2613  										New:  "bam",
  2614  									},
  2615  									{
  2616  										Type: DiffTypeAdded,
  2617  										Name: "Path",
  2618  										Old:  "",
  2619  										New:  "foo",
  2620  									},
  2621  									{
  2622  										Type: DiffTypeAdded,
  2623  										Name: "Protocol",
  2624  										Old:  "",
  2625  										New:  "http",
  2626  									},
  2627  									{
  2628  										Type: DiffTypeAdded,
  2629  										Name: "Timeout",
  2630  										Old:  "",
  2631  										New:  "1000000000",
  2632  									},
  2633  									{
  2634  										Type: DiffTypeAdded,
  2635  										Name: "Type",
  2636  										Old:  "",
  2637  										New:  "http",
  2638  									},
  2639  								},
  2640  							},
  2641  							{
  2642  								Type: DiffTypeDeleted,
  2643  								Name: "Check",
  2644  								Fields: []*FieldDiff{
  2645  									{
  2646  										Type: DiffTypeDeleted,
  2647  										Name: "Command",
  2648  										Old:  "foo",
  2649  										New:  "",
  2650  									},
  2651  									{
  2652  										Type: DiffTypeDeleted,
  2653  										Name: "Interval",
  2654  										Old:  "1000000000",
  2655  										New:  "",
  2656  									},
  2657  									{
  2658  										Type: DiffTypeDeleted,
  2659  										Name: "Name",
  2660  										Old:  "foo",
  2661  										New:  "",
  2662  									},
  2663  									{
  2664  										Type: DiffTypeDeleted,
  2665  										Name: "Path",
  2666  										Old:  "foo",
  2667  										New:  "",
  2668  									},
  2669  									{
  2670  										Type: DiffTypeDeleted,
  2671  										Name: "Protocol",
  2672  										Old:  "http",
  2673  										New:  "",
  2674  									},
  2675  									{
  2676  										Type: DiffTypeDeleted,
  2677  										Name: "Timeout",
  2678  										Old:  "1000000000",
  2679  										New:  "",
  2680  									},
  2681  									{
  2682  										Type: DiffTypeDeleted,
  2683  										Name: "Type",
  2684  										Old:  "http",
  2685  										New:  "",
  2686  									},
  2687  								},
  2688  							},
  2689  						},
  2690  					},
  2691  				},
  2692  			},
  2693  		},
  2694  		{
  2695  			// Service Checks edited with context
  2696  			Contextual: true,
  2697  			Old: &Task{
  2698  				Services: []*Service{
  2699  					{
  2700  						Name: "foo",
  2701  						Checks: []*ServiceCheck{
  2702  							{
  2703  								Name:     "foo",
  2704  								Type:     "http",
  2705  								Command:  "foo",
  2706  								Args:     []string{"foo"},
  2707  								Path:     "foo",
  2708  								Protocol: "http",
  2709  								Interval: 1 * time.Second,
  2710  								Timeout:  1 * time.Second,
  2711  							},
  2712  						},
  2713  					},
  2714  				},
  2715  			},
  2716  			New: &Task{
  2717  				Services: []*Service{
  2718  					{
  2719  						Name: "foo",
  2720  						Checks: []*ServiceCheck{
  2721  							{
  2722  								Name:     "foo",
  2723  								Type:     "tcp",
  2724  								Command:  "foo",
  2725  								Args:     []string{"foo"},
  2726  								Path:     "foo",
  2727  								Protocol: "http",
  2728  								Interval: 1 * time.Second,
  2729  								Timeout:  1 * time.Second,
  2730  							},
  2731  						},
  2732  					},
  2733  				},
  2734  			},
  2735  			Expected: &TaskDiff{
  2736  				Type: DiffTypeEdited,
  2737  				Objects: []*ObjectDiff{
  2738  					{
  2739  						Type: DiffTypeEdited,
  2740  						Name: "Service",
  2741  						Fields: []*FieldDiff{
  2742  							{
  2743  								Type: DiffTypeNone,
  2744  								Name: "Name",
  2745  								Old:  "foo",
  2746  								New:  "foo",
  2747  							},
  2748  							{
  2749  								Type: DiffTypeNone,
  2750  								Name: "PortLabel",
  2751  								Old:  "",
  2752  								New:  "",
  2753  							},
  2754  						},
  2755  						Objects: []*ObjectDiff{
  2756  							{
  2757  								Type: DiffTypeEdited,
  2758  								Name: "Check",
  2759  								Fields: []*FieldDiff{
  2760  									{
  2761  										Type: DiffTypeNone,
  2762  										Name: "Command",
  2763  										Old:  "foo",
  2764  										New:  "foo",
  2765  									},
  2766  									{
  2767  										Type: DiffTypeNone,
  2768  										Name: "Interval",
  2769  										Old:  "1000000000",
  2770  										New:  "1000000000",
  2771  									},
  2772  									{
  2773  										Type: DiffTypeNone,
  2774  										Name: "Name",
  2775  										Old:  "foo",
  2776  										New:  "foo",
  2777  									},
  2778  									{
  2779  										Type: DiffTypeNone,
  2780  										Name: "Path",
  2781  										Old:  "foo",
  2782  										New:  "foo",
  2783  									},
  2784  									{
  2785  										Type: DiffTypeNone,
  2786  										Name: "Protocol",
  2787  										Old:  "http",
  2788  										New:  "http",
  2789  									},
  2790  									{
  2791  										Type: DiffTypeNone,
  2792  										Name: "Timeout",
  2793  										Old:  "1000000000",
  2794  										New:  "1000000000",
  2795  									},
  2796  									{
  2797  										Type: DiffTypeEdited,
  2798  										Name: "Type",
  2799  										Old:  "http",
  2800  										New:  "tcp",
  2801  									},
  2802  								},
  2803  							},
  2804  						},
  2805  					},
  2806  				},
  2807  			},
  2808  		},
  2809  	}
  2810  
  2811  	for i, c := range cases {
  2812  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2813  		if c.Error && err == nil {
  2814  			t.Fatalf("case %d: expected errored")
  2815  		} else if err != nil {
  2816  			if !c.Error {
  2817  				t.Fatalf("case %d: errored %#v", i+1, err)
  2818  			} else {
  2819  				continue
  2820  			}
  2821  		}
  2822  
  2823  		if !reflect.DeepEqual(actual, c.Expected) {
  2824  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2825  				i+1, actual, c.Expected)
  2826  		}
  2827  	}
  2828  }