github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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", i+1)
   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  			// EphemeralDisk added
  1281  			Old: &TaskGroup{},
  1282  			New: &TaskGroup{
  1283  				EphemeralDisk: &EphemeralDisk{
  1284  					Sticky: true,
  1285  					SizeMB: 100,
  1286  				},
  1287  			},
  1288  			Expected: &TaskGroupDiff{
  1289  				Type: DiffTypeEdited,
  1290  				Objects: []*ObjectDiff{
  1291  					{
  1292  						Type: DiffTypeAdded,
  1293  						Name: "EphemeralDisk",
  1294  						Fields: []*FieldDiff{
  1295  							{
  1296  								Type: DiffTypeAdded,
  1297  								Name: "SizeMB",
  1298  								Old:  "",
  1299  								New:  "100",
  1300  							},
  1301  							{
  1302  								Type: DiffTypeAdded,
  1303  								Name: "Sticky",
  1304  								Old:  "",
  1305  								New:  "true",
  1306  							},
  1307  						},
  1308  					},
  1309  				},
  1310  			},
  1311  		},
  1312  		{
  1313  			// EphemeralDisk deleted
  1314  			Old: &TaskGroup{
  1315  				EphemeralDisk: &EphemeralDisk{
  1316  					Sticky: true,
  1317  					SizeMB: 100,
  1318  				},
  1319  			},
  1320  			New: &TaskGroup{},
  1321  			Expected: &TaskGroupDiff{
  1322  				Type: DiffTypeEdited,
  1323  				Objects: []*ObjectDiff{
  1324  					{
  1325  						Type: DiffTypeDeleted,
  1326  						Name: "EphemeralDisk",
  1327  						Fields: []*FieldDiff{
  1328  							{
  1329  								Type: DiffTypeDeleted,
  1330  								Name: "SizeMB",
  1331  								Old:  "100",
  1332  								New:  "",
  1333  							},
  1334  							{
  1335  								Type: DiffTypeDeleted,
  1336  								Name: "Sticky",
  1337  								Old:  "true",
  1338  								New:  "",
  1339  							},
  1340  						},
  1341  					},
  1342  				},
  1343  			},
  1344  		},
  1345  		{
  1346  			// EphemeralDisk edited
  1347  			Old: &TaskGroup{
  1348  				EphemeralDisk: &EphemeralDisk{
  1349  					Sticky: true,
  1350  					SizeMB: 150,
  1351  				},
  1352  			},
  1353  			New: &TaskGroup{
  1354  				EphemeralDisk: &EphemeralDisk{
  1355  					Sticky: false,
  1356  					SizeMB: 90,
  1357  				},
  1358  			},
  1359  			Expected: &TaskGroupDiff{
  1360  				Type: DiffTypeEdited,
  1361  				Objects: []*ObjectDiff{
  1362  					{
  1363  						Type: DiffTypeEdited,
  1364  						Name: "EphemeralDisk",
  1365  						Fields: []*FieldDiff{
  1366  							{
  1367  								Type: DiffTypeEdited,
  1368  								Name: "SizeMB",
  1369  								Old:  "150",
  1370  								New:  "90",
  1371  							},
  1372  
  1373  							{
  1374  								Type: DiffTypeEdited,
  1375  								Name: "Sticky",
  1376  								Old:  "true",
  1377  								New:  "false",
  1378  							},
  1379  						},
  1380  					},
  1381  				},
  1382  			},
  1383  		},
  1384  		{
  1385  			// EphemeralDisk edited with context
  1386  			Contextual: true,
  1387  			Old: &TaskGroup{
  1388  				EphemeralDisk: &EphemeralDisk{
  1389  					Sticky: false,
  1390  					SizeMB: 100,
  1391  				},
  1392  			},
  1393  			New: &TaskGroup{
  1394  				EphemeralDisk: &EphemeralDisk{
  1395  					Sticky: true,
  1396  					SizeMB: 90,
  1397  				},
  1398  			},
  1399  			Expected: &TaskGroupDiff{
  1400  				Type: DiffTypeEdited,
  1401  				Objects: []*ObjectDiff{
  1402  					{
  1403  						Type: DiffTypeEdited,
  1404  						Name: "EphemeralDisk",
  1405  						Fields: []*FieldDiff{
  1406  							{
  1407  								Type: DiffTypeEdited,
  1408  								Name: "SizeMB",
  1409  								Old:  "100",
  1410  								New:  "90",
  1411  							},
  1412  
  1413  							{
  1414  								Type: DiffTypeEdited,
  1415  								Name: "Sticky",
  1416  								Old:  "false",
  1417  								New:  "true",
  1418  							},
  1419  						},
  1420  					},
  1421  				},
  1422  			},
  1423  		},
  1424  		{
  1425  			// Tasks edited
  1426  			Old: &TaskGroup{
  1427  				Tasks: []*Task{
  1428  					{
  1429  						Name:   "foo",
  1430  						Driver: "docker",
  1431  					},
  1432  					{
  1433  						Name:   "bar",
  1434  						Driver: "docker",
  1435  					},
  1436  					{
  1437  						Name:   "baz",
  1438  						Driver: "docker",
  1439  					},
  1440  				},
  1441  			},
  1442  			New: &TaskGroup{
  1443  				Tasks: []*Task{
  1444  					{
  1445  						Name:   "bar",
  1446  						Driver: "docker",
  1447  					},
  1448  					{
  1449  						Name:   "baz",
  1450  						Driver: "exec",
  1451  					},
  1452  					{
  1453  						Name:   "bam",
  1454  						Driver: "docker",
  1455  					},
  1456  				},
  1457  			},
  1458  			Expected: &TaskGroupDiff{
  1459  				Type: DiffTypeEdited,
  1460  				Tasks: []*TaskDiff{
  1461  					{
  1462  						Type: DiffTypeAdded,
  1463  						Name: "bam",
  1464  						Fields: []*FieldDiff{
  1465  							{
  1466  								Type: DiffTypeAdded,
  1467  								Name: "Driver",
  1468  								Old:  "",
  1469  								New:  "docker",
  1470  							},
  1471  							{
  1472  								Type: DiffTypeAdded,
  1473  								Name: "ExcludeNomadEnv",
  1474  								Old:  "",
  1475  								New:  "false",
  1476  							},
  1477  							{
  1478  								Type: DiffTypeAdded,
  1479  								Name: "KillTimeout",
  1480  								Old:  "",
  1481  								New:  "0",
  1482  							},
  1483  						},
  1484  					},
  1485  					{
  1486  						Type: DiffTypeNone,
  1487  						Name: "bar",
  1488  					},
  1489  					{
  1490  						Type: DiffTypeEdited,
  1491  						Name: "baz",
  1492  						Fields: []*FieldDiff{
  1493  							{
  1494  								Type: DiffTypeEdited,
  1495  								Name: "Driver",
  1496  								Old:  "docker",
  1497  								New:  "exec",
  1498  							},
  1499  						},
  1500  					},
  1501  					{
  1502  						Type: DiffTypeDeleted,
  1503  						Name: "foo",
  1504  						Fields: []*FieldDiff{
  1505  							{
  1506  								Type: DiffTypeDeleted,
  1507  								Name: "Driver",
  1508  								Old:  "docker",
  1509  								New:  "",
  1510  							},
  1511  							{
  1512  								Type: DiffTypeDeleted,
  1513  								Name: "ExcludeNomadEnv",
  1514  								Old:  "false",
  1515  								New:  "",
  1516  							},
  1517  							{
  1518  								Type: DiffTypeDeleted,
  1519  								Name: "KillTimeout",
  1520  								Old:  "0",
  1521  								New:  "",
  1522  							},
  1523  						},
  1524  					},
  1525  				},
  1526  			},
  1527  		},
  1528  	}
  1529  
  1530  	for i, c := range cases {
  1531  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1532  		if c.Error && err == nil {
  1533  			t.Fatalf("case %d: expected errored")
  1534  		} else if err != nil {
  1535  			if !c.Error {
  1536  				t.Fatalf("case %d: errored %#v", i+1, err)
  1537  			} else {
  1538  				continue
  1539  			}
  1540  		}
  1541  
  1542  		if !reflect.DeepEqual(actual, c.Expected) {
  1543  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1544  				i+1, actual, c.Expected)
  1545  		}
  1546  	}
  1547  }
  1548  
  1549  func TestTaskDiff(t *testing.T) {
  1550  	cases := []struct {
  1551  		Old, New   *Task
  1552  		Expected   *TaskDiff
  1553  		Error      bool
  1554  		Contextual bool
  1555  	}{
  1556  		{
  1557  			Old: nil,
  1558  			New: nil,
  1559  			Expected: &TaskDiff{
  1560  				Type: DiffTypeNone,
  1561  			},
  1562  		},
  1563  		{
  1564  			// Primitive only that has different names
  1565  			Old: &Task{
  1566  				Name: "foo",
  1567  				Meta: map[string]string{
  1568  					"foo": "bar",
  1569  				},
  1570  			},
  1571  			New: &Task{
  1572  				Name: "bar",
  1573  				Meta: map[string]string{
  1574  					"foo": "bar",
  1575  				},
  1576  			},
  1577  			Error: true,
  1578  		},
  1579  		{
  1580  			// Primitive only that is the same
  1581  			Old: &Task{
  1582  				Name:   "foo",
  1583  				Driver: "exec",
  1584  				User:   "foo",
  1585  				Env: map[string]string{
  1586  					"FOO": "bar",
  1587  				},
  1588  				Meta: map[string]string{
  1589  					"foo": "bar",
  1590  				},
  1591  				KillTimeout: 1 * time.Second,
  1592  			},
  1593  			New: &Task{
  1594  				Name:   "foo",
  1595  				Driver: "exec",
  1596  				User:   "foo",
  1597  				Env: map[string]string{
  1598  					"FOO": "bar",
  1599  				},
  1600  				Meta: map[string]string{
  1601  					"foo": "bar",
  1602  				},
  1603  				KillTimeout: 1 * time.Second,
  1604  			},
  1605  			Expected: &TaskDiff{
  1606  				Type: DiffTypeNone,
  1607  				Name: "foo",
  1608  			},
  1609  		},
  1610  		{
  1611  			// Primitive only that has diffs
  1612  			Old: &Task{
  1613  				Name:   "foo",
  1614  				Driver: "exec",
  1615  				User:   "foo",
  1616  				Env: map[string]string{
  1617  					"FOO": "bar",
  1618  				},
  1619  				Meta: map[string]string{
  1620  					"foo": "bar",
  1621  				},
  1622  				KillTimeout: 1 * time.Second,
  1623  			},
  1624  			New: &Task{
  1625  				Name:   "foo",
  1626  				Driver: "docker",
  1627  				User:   "bar",
  1628  				Env: map[string]string{
  1629  					"FOO": "baz",
  1630  				},
  1631  				Meta: map[string]string{
  1632  					"foo": "baz",
  1633  				},
  1634  				KillTimeout: 2 * time.Second,
  1635  			},
  1636  			Expected: &TaskDiff{
  1637  				Type: DiffTypeEdited,
  1638  				Name: "foo",
  1639  				Fields: []*FieldDiff{
  1640  					{
  1641  						Type: DiffTypeEdited,
  1642  						Name: "Driver",
  1643  						Old:  "exec",
  1644  						New:  "docker",
  1645  					},
  1646  					{
  1647  						Type: DiffTypeEdited,
  1648  						Name: "Env[FOO]",
  1649  						Old:  "bar",
  1650  						New:  "baz",
  1651  					},
  1652  					{
  1653  						Type: DiffTypeEdited,
  1654  						Name: "KillTimeout",
  1655  						Old:  "1000000000",
  1656  						New:  "2000000000",
  1657  					},
  1658  					{
  1659  						Type: DiffTypeEdited,
  1660  						Name: "Meta[foo]",
  1661  						Old:  "bar",
  1662  						New:  "baz",
  1663  					},
  1664  					{
  1665  						Type: DiffTypeEdited,
  1666  						Name: "User",
  1667  						Old:  "foo",
  1668  						New:  "bar",
  1669  					},
  1670  				},
  1671  			},
  1672  		},
  1673  		{
  1674  			// Map diff
  1675  			Old: &Task{
  1676  				Meta: map[string]string{
  1677  					"foo": "foo",
  1678  					"bar": "bar",
  1679  				},
  1680  				Env: map[string]string{
  1681  					"foo": "foo",
  1682  					"bar": "bar",
  1683  				},
  1684  			},
  1685  			New: &Task{
  1686  				Meta: map[string]string{
  1687  					"bar": "bar",
  1688  					"baz": "baz",
  1689  				},
  1690  				Env: map[string]string{
  1691  					"bar": "bar",
  1692  					"baz": "baz",
  1693  				},
  1694  			},
  1695  			Expected: &TaskDiff{
  1696  				Type: DiffTypeEdited,
  1697  				Fields: []*FieldDiff{
  1698  					{
  1699  						Type: DiffTypeAdded,
  1700  						Name: "Env[baz]",
  1701  						Old:  "",
  1702  						New:  "baz",
  1703  					},
  1704  					{
  1705  						Type: DiffTypeDeleted,
  1706  						Name: "Env[foo]",
  1707  						Old:  "foo",
  1708  						New:  "",
  1709  					},
  1710  					{
  1711  						Type: DiffTypeAdded,
  1712  						Name: "Meta[baz]",
  1713  						Old:  "",
  1714  						New:  "baz",
  1715  					},
  1716  					{
  1717  						Type: DiffTypeDeleted,
  1718  						Name: "Meta[foo]",
  1719  						Old:  "foo",
  1720  						New:  "",
  1721  					},
  1722  				},
  1723  			},
  1724  		},
  1725  		{
  1726  			// Constraints edited
  1727  			Old: &Task{
  1728  				Constraints: []*Constraint{
  1729  					{
  1730  						LTarget: "foo",
  1731  						RTarget: "foo",
  1732  						Operand: "foo",
  1733  						str:     "foo",
  1734  					},
  1735  					{
  1736  						LTarget: "bar",
  1737  						RTarget: "bar",
  1738  						Operand: "bar",
  1739  						str:     "bar",
  1740  					},
  1741  				},
  1742  			},
  1743  			New: &Task{
  1744  				Constraints: []*Constraint{
  1745  					{
  1746  						LTarget: "foo",
  1747  						RTarget: "foo",
  1748  						Operand: "foo",
  1749  						str:     "foo",
  1750  					},
  1751  					{
  1752  						LTarget: "baz",
  1753  						RTarget: "baz",
  1754  						Operand: "baz",
  1755  						str:     "baz",
  1756  					},
  1757  				},
  1758  			},
  1759  			Expected: &TaskDiff{
  1760  				Type: DiffTypeEdited,
  1761  				Objects: []*ObjectDiff{
  1762  					{
  1763  						Type: DiffTypeAdded,
  1764  						Name: "Constraint",
  1765  						Fields: []*FieldDiff{
  1766  							{
  1767  								Type: DiffTypeAdded,
  1768  								Name: "LTarget",
  1769  								Old:  "",
  1770  								New:  "baz",
  1771  							},
  1772  							{
  1773  								Type: DiffTypeAdded,
  1774  								Name: "Operand",
  1775  								Old:  "",
  1776  								New:  "baz",
  1777  							},
  1778  							{
  1779  								Type: DiffTypeAdded,
  1780  								Name: "RTarget",
  1781  								Old:  "",
  1782  								New:  "baz",
  1783  							},
  1784  						},
  1785  					},
  1786  					{
  1787  						Type: DiffTypeDeleted,
  1788  						Name: "Constraint",
  1789  						Fields: []*FieldDiff{
  1790  							{
  1791  								Type: DiffTypeDeleted,
  1792  								Name: "LTarget",
  1793  								Old:  "bar",
  1794  								New:  "",
  1795  							},
  1796  							{
  1797  								Type: DiffTypeDeleted,
  1798  								Name: "Operand",
  1799  								Old:  "bar",
  1800  								New:  "",
  1801  							},
  1802  							{
  1803  								Type: DiffTypeDeleted,
  1804  								Name: "RTarget",
  1805  								Old:  "bar",
  1806  								New:  "",
  1807  							},
  1808  						},
  1809  					},
  1810  				},
  1811  			},
  1812  		},
  1813  		{
  1814  			// LogConfig added
  1815  			Old: &Task{},
  1816  			New: &Task{
  1817  				LogConfig: &LogConfig{
  1818  					MaxFiles:      1,
  1819  					MaxFileSizeMB: 10,
  1820  				},
  1821  			},
  1822  			Expected: &TaskDiff{
  1823  				Type: DiffTypeEdited,
  1824  				Objects: []*ObjectDiff{
  1825  					{
  1826  						Type: DiffTypeAdded,
  1827  						Name: "LogConfig",
  1828  						Fields: []*FieldDiff{
  1829  							{
  1830  								Type: DiffTypeAdded,
  1831  								Name: "MaxFileSizeMB",
  1832  								Old:  "",
  1833  								New:  "10",
  1834  							},
  1835  							{
  1836  								Type: DiffTypeAdded,
  1837  								Name: "MaxFiles",
  1838  								Old:  "",
  1839  								New:  "1",
  1840  							},
  1841  						},
  1842  					},
  1843  				},
  1844  			},
  1845  		},
  1846  		{
  1847  			// LogConfig deleted
  1848  			Old: &Task{
  1849  				LogConfig: &LogConfig{
  1850  					MaxFiles:      1,
  1851  					MaxFileSizeMB: 10,
  1852  				},
  1853  			},
  1854  			New: &Task{},
  1855  			Expected: &TaskDiff{
  1856  				Type: DiffTypeEdited,
  1857  				Objects: []*ObjectDiff{
  1858  					{
  1859  						Type: DiffTypeDeleted,
  1860  						Name: "LogConfig",
  1861  						Fields: []*FieldDiff{
  1862  							{
  1863  								Type: DiffTypeDeleted,
  1864  								Name: "MaxFileSizeMB",
  1865  								Old:  "10",
  1866  								New:  "",
  1867  							},
  1868  							{
  1869  								Type: DiffTypeDeleted,
  1870  								Name: "MaxFiles",
  1871  								Old:  "1",
  1872  								New:  "",
  1873  							},
  1874  						},
  1875  					},
  1876  				},
  1877  			},
  1878  		},
  1879  		{
  1880  			// LogConfig edited
  1881  			Old: &Task{
  1882  				LogConfig: &LogConfig{
  1883  					MaxFiles:      1,
  1884  					MaxFileSizeMB: 10,
  1885  				},
  1886  			},
  1887  			New: &Task{
  1888  				LogConfig: &LogConfig{
  1889  					MaxFiles:      2,
  1890  					MaxFileSizeMB: 20,
  1891  				},
  1892  			},
  1893  			Expected: &TaskDiff{
  1894  				Type: DiffTypeEdited,
  1895  				Objects: []*ObjectDiff{
  1896  					{
  1897  						Type: DiffTypeEdited,
  1898  						Name: "LogConfig",
  1899  						Fields: []*FieldDiff{
  1900  							{
  1901  								Type: DiffTypeEdited,
  1902  								Name: "MaxFileSizeMB",
  1903  								Old:  "10",
  1904  								New:  "20",
  1905  							},
  1906  							{
  1907  								Type: DiffTypeEdited,
  1908  								Name: "MaxFiles",
  1909  								Old:  "1",
  1910  								New:  "2",
  1911  							},
  1912  						},
  1913  					},
  1914  				},
  1915  			},
  1916  		},
  1917  		{
  1918  			// LogConfig edited with context
  1919  			Contextual: true,
  1920  			Old: &Task{
  1921  				LogConfig: &LogConfig{
  1922  					MaxFiles:      1,
  1923  					MaxFileSizeMB: 10,
  1924  				},
  1925  			},
  1926  			New: &Task{
  1927  				LogConfig: &LogConfig{
  1928  					MaxFiles:      1,
  1929  					MaxFileSizeMB: 20,
  1930  				},
  1931  			},
  1932  			Expected: &TaskDiff{
  1933  				Type: DiffTypeEdited,
  1934  				Objects: []*ObjectDiff{
  1935  					{
  1936  						Type: DiffTypeEdited,
  1937  						Name: "LogConfig",
  1938  						Fields: []*FieldDiff{
  1939  							{
  1940  								Type: DiffTypeEdited,
  1941  								Name: "MaxFileSizeMB",
  1942  								Old:  "10",
  1943  								New:  "20",
  1944  							},
  1945  							{
  1946  								Type: DiffTypeNone,
  1947  								Name: "MaxFiles",
  1948  								Old:  "1",
  1949  								New:  "1",
  1950  							},
  1951  						},
  1952  					},
  1953  				},
  1954  			},
  1955  		},
  1956  		{
  1957  			// Artifacts edited
  1958  			Old: &Task{
  1959  				Artifacts: []*TaskArtifact{
  1960  					{
  1961  						GetterSource: "foo",
  1962  						GetterOptions: map[string]string{
  1963  							"foo": "bar",
  1964  						},
  1965  						RelativeDest: "foo",
  1966  					},
  1967  					{
  1968  						GetterSource: "bar",
  1969  						GetterOptions: map[string]string{
  1970  							"bar": "baz",
  1971  						},
  1972  						RelativeDest: "bar",
  1973  					},
  1974  				},
  1975  			},
  1976  			New: &Task{
  1977  				Artifacts: []*TaskArtifact{
  1978  					{
  1979  						GetterSource: "foo",
  1980  						GetterOptions: map[string]string{
  1981  							"foo": "bar",
  1982  						},
  1983  						RelativeDest: "foo",
  1984  					},
  1985  					{
  1986  						GetterSource: "bam",
  1987  						GetterOptions: map[string]string{
  1988  							"bam": "baz",
  1989  						},
  1990  						RelativeDest: "bam",
  1991  					},
  1992  				},
  1993  			},
  1994  			Expected: &TaskDiff{
  1995  				Type: DiffTypeEdited,
  1996  				Objects: []*ObjectDiff{
  1997  					{
  1998  						Type: DiffTypeAdded,
  1999  						Name: "Artifact",
  2000  						Fields: []*FieldDiff{
  2001  							{
  2002  								Type: DiffTypeAdded,
  2003  								Name: "GetterOptions[bam]",
  2004  								Old:  "",
  2005  								New:  "baz",
  2006  							},
  2007  							{
  2008  								Type: DiffTypeAdded,
  2009  								Name: "GetterSource",
  2010  								Old:  "",
  2011  								New:  "bam",
  2012  							},
  2013  							{
  2014  								Type: DiffTypeAdded,
  2015  								Name: "RelativeDest",
  2016  								Old:  "",
  2017  								New:  "bam",
  2018  							},
  2019  						},
  2020  					},
  2021  					{
  2022  						Type: DiffTypeDeleted,
  2023  						Name: "Artifact",
  2024  						Fields: []*FieldDiff{
  2025  							{
  2026  								Type: DiffTypeDeleted,
  2027  								Name: "GetterOptions[bar]",
  2028  								Old:  "baz",
  2029  								New:  "",
  2030  							},
  2031  							{
  2032  								Type: DiffTypeDeleted,
  2033  								Name: "GetterSource",
  2034  								Old:  "bar",
  2035  								New:  "",
  2036  							},
  2037  							{
  2038  								Type: DiffTypeDeleted,
  2039  								Name: "RelativeDest",
  2040  								Old:  "bar",
  2041  								New:  "",
  2042  							},
  2043  						},
  2044  					},
  2045  				},
  2046  			},
  2047  		},
  2048  		{
  2049  			// Resources edited (no networks)
  2050  			Old: &Task{
  2051  				Resources: &Resources{
  2052  					CPU:      100,
  2053  					MemoryMB: 100,
  2054  					DiskMB:   100,
  2055  					IOPS:     100,
  2056  				},
  2057  			},
  2058  			New: &Task{
  2059  				Resources: &Resources{
  2060  					CPU:      200,
  2061  					MemoryMB: 200,
  2062  					DiskMB:   200,
  2063  					IOPS:     200,
  2064  				},
  2065  			},
  2066  			Expected: &TaskDiff{
  2067  				Type: DiffTypeEdited,
  2068  				Objects: []*ObjectDiff{
  2069  					{
  2070  						Type: DiffTypeEdited,
  2071  						Name: "Resources",
  2072  						Fields: []*FieldDiff{
  2073  							{
  2074  								Type: DiffTypeEdited,
  2075  								Name: "CPU",
  2076  								Old:  "100",
  2077  								New:  "200",
  2078  							},
  2079  							{
  2080  								Type: DiffTypeEdited,
  2081  								Name: "DiskMB",
  2082  								Old:  "100",
  2083  								New:  "200",
  2084  							},
  2085  							{
  2086  								Type: DiffTypeEdited,
  2087  								Name: "IOPS",
  2088  								Old:  "100",
  2089  								New:  "200",
  2090  							},
  2091  							{
  2092  								Type: DiffTypeEdited,
  2093  								Name: "MemoryMB",
  2094  								Old:  "100",
  2095  								New:  "200",
  2096  							},
  2097  						},
  2098  					},
  2099  				},
  2100  			},
  2101  		},
  2102  		{
  2103  			// Resources edited (no networks) with context
  2104  			Contextual: true,
  2105  			Old: &Task{
  2106  				Resources: &Resources{
  2107  					CPU:      100,
  2108  					MemoryMB: 100,
  2109  					DiskMB:   100,
  2110  					IOPS:     100,
  2111  				},
  2112  			},
  2113  			New: &Task{
  2114  				Resources: &Resources{
  2115  					CPU:      200,
  2116  					MemoryMB: 100,
  2117  					DiskMB:   200,
  2118  					IOPS:     100,
  2119  				},
  2120  			},
  2121  			Expected: &TaskDiff{
  2122  				Type: DiffTypeEdited,
  2123  				Objects: []*ObjectDiff{
  2124  					{
  2125  						Type: DiffTypeEdited,
  2126  						Name: "Resources",
  2127  						Fields: []*FieldDiff{
  2128  							{
  2129  								Type: DiffTypeEdited,
  2130  								Name: "CPU",
  2131  								Old:  "100",
  2132  								New:  "200",
  2133  							},
  2134  							{
  2135  								Type: DiffTypeEdited,
  2136  								Name: "DiskMB",
  2137  								Old:  "100",
  2138  								New:  "200",
  2139  							},
  2140  							{
  2141  								Type: DiffTypeNone,
  2142  								Name: "IOPS",
  2143  								Old:  "100",
  2144  								New:  "100",
  2145  							},
  2146  							{
  2147  								Type: DiffTypeNone,
  2148  								Name: "MemoryMB",
  2149  								Old:  "100",
  2150  								New:  "100",
  2151  							},
  2152  						},
  2153  					},
  2154  				},
  2155  			},
  2156  		},
  2157  		{
  2158  			// Network Resources edited
  2159  			Old: &Task{
  2160  				Resources: &Resources{
  2161  					Networks: []*NetworkResource{
  2162  						{
  2163  							Device: "foo",
  2164  							CIDR:   "foo",
  2165  							IP:     "foo",
  2166  							MBits:  100,
  2167  							ReservedPorts: []Port{
  2168  								{
  2169  									Label: "foo",
  2170  									Value: 80,
  2171  								},
  2172  							},
  2173  							DynamicPorts: []Port{
  2174  								{
  2175  									Label: "bar",
  2176  								},
  2177  							},
  2178  						},
  2179  					},
  2180  				},
  2181  			},
  2182  			New: &Task{
  2183  				Resources: &Resources{
  2184  					Networks: []*NetworkResource{
  2185  						{
  2186  							Device: "bar",
  2187  							CIDR:   "bar",
  2188  							IP:     "bar",
  2189  							MBits:  200,
  2190  							ReservedPorts: []Port{
  2191  								{
  2192  									Label: "foo",
  2193  									Value: 81,
  2194  								},
  2195  							},
  2196  							DynamicPorts: []Port{
  2197  								{
  2198  									Label: "baz",
  2199  								},
  2200  							},
  2201  						},
  2202  					},
  2203  				},
  2204  			},
  2205  			Expected: &TaskDiff{
  2206  				Type: DiffTypeEdited,
  2207  				Objects: []*ObjectDiff{
  2208  					{
  2209  						Type: DiffTypeEdited,
  2210  						Name: "Resources",
  2211  						Objects: []*ObjectDiff{
  2212  							{
  2213  								Type: DiffTypeAdded,
  2214  								Name: "Network",
  2215  								Fields: []*FieldDiff{
  2216  									{
  2217  										Type: DiffTypeAdded,
  2218  										Name: "MBits",
  2219  										Old:  "",
  2220  										New:  "200",
  2221  									},
  2222  								},
  2223  								Objects: []*ObjectDiff{
  2224  									{
  2225  										Type: DiffTypeAdded,
  2226  										Name: "Static Port",
  2227  										Fields: []*FieldDiff{
  2228  											{
  2229  												Type: DiffTypeAdded,
  2230  												Name: "Label",
  2231  												Old:  "",
  2232  												New:  "foo",
  2233  											},
  2234  											{
  2235  												Type: DiffTypeAdded,
  2236  												Name: "Value",
  2237  												Old:  "",
  2238  												New:  "81",
  2239  											},
  2240  										},
  2241  									},
  2242  									{
  2243  										Type: DiffTypeAdded,
  2244  										Name: "Dynamic Port",
  2245  										Fields: []*FieldDiff{
  2246  											{
  2247  												Type: DiffTypeAdded,
  2248  												Name: "Label",
  2249  												Old:  "",
  2250  												New:  "baz",
  2251  											},
  2252  										},
  2253  									},
  2254  								},
  2255  							},
  2256  							{
  2257  								Type: DiffTypeDeleted,
  2258  								Name: "Network",
  2259  								Fields: []*FieldDiff{
  2260  									{
  2261  										Type: DiffTypeDeleted,
  2262  										Name: "MBits",
  2263  										Old:  "100",
  2264  										New:  "",
  2265  									},
  2266  								},
  2267  								Objects: []*ObjectDiff{
  2268  									{
  2269  										Type: DiffTypeDeleted,
  2270  										Name: "Static Port",
  2271  										Fields: []*FieldDiff{
  2272  											{
  2273  												Type: DiffTypeDeleted,
  2274  												Name: "Label",
  2275  												Old:  "foo",
  2276  												New:  "",
  2277  											},
  2278  											{
  2279  												Type: DiffTypeDeleted,
  2280  												Name: "Value",
  2281  												Old:  "80",
  2282  												New:  "",
  2283  											},
  2284  										},
  2285  									},
  2286  									{
  2287  										Type: DiffTypeDeleted,
  2288  										Name: "Dynamic Port",
  2289  										Fields: []*FieldDiff{
  2290  											{
  2291  												Type: DiffTypeDeleted,
  2292  												Name: "Label",
  2293  												Old:  "bar",
  2294  												New:  "",
  2295  											},
  2296  										},
  2297  									},
  2298  								},
  2299  							},
  2300  						},
  2301  					},
  2302  				},
  2303  			},
  2304  		},
  2305  		{
  2306  			// Config same
  2307  			Old: &Task{
  2308  				Config: map[string]interface{}{
  2309  					"foo": 1,
  2310  					"bar": "bar",
  2311  					"bam": []string{"a", "b"},
  2312  					"baz": map[string]int{
  2313  						"a": 1,
  2314  						"b": 2,
  2315  					},
  2316  					"boom": &Port{
  2317  						Label: "boom_port",
  2318  					},
  2319  				},
  2320  			},
  2321  			New: &Task{
  2322  				Config: map[string]interface{}{
  2323  					"foo": 1,
  2324  					"bar": "bar",
  2325  					"bam": []string{"a", "b"},
  2326  					"baz": map[string]int{
  2327  						"a": 1,
  2328  						"b": 2,
  2329  					},
  2330  					"boom": &Port{
  2331  						Label: "boom_port",
  2332  					},
  2333  				},
  2334  			},
  2335  			Expected: &TaskDiff{
  2336  				Type: DiffTypeNone,
  2337  			},
  2338  		},
  2339  		{
  2340  			// Config edited
  2341  			Old: &Task{
  2342  				Config: map[string]interface{}{
  2343  					"foo": 1,
  2344  					"bar": "baz",
  2345  					"bam": []string{"a", "b"},
  2346  					"baz": map[string]int{
  2347  						"a": 1,
  2348  						"b": 2,
  2349  					},
  2350  					"boom": &Port{
  2351  						Label: "boom_port",
  2352  					},
  2353  				},
  2354  			},
  2355  			New: &Task{
  2356  				Config: map[string]interface{}{
  2357  					"foo": 2,
  2358  					"bar": "baz",
  2359  					"bam": []string{"a", "c", "d"},
  2360  					"baz": map[string]int{
  2361  						"b": 3,
  2362  						"c": 4,
  2363  					},
  2364  					"boom": &Port{
  2365  						Label: "boom_port2",
  2366  					},
  2367  				},
  2368  			},
  2369  			Expected: &TaskDiff{
  2370  				Type: DiffTypeEdited,
  2371  				Objects: []*ObjectDiff{
  2372  					{
  2373  						Type: DiffTypeEdited,
  2374  						Name: "Config",
  2375  						Fields: []*FieldDiff{
  2376  							{
  2377  								Type: DiffTypeEdited,
  2378  								Name: "bam[1]",
  2379  								Old:  "b",
  2380  								New:  "c",
  2381  							},
  2382  							{
  2383  								Type: DiffTypeAdded,
  2384  								Name: "bam[2]",
  2385  								Old:  "",
  2386  								New:  "d",
  2387  							},
  2388  							{
  2389  								Type: DiffTypeDeleted,
  2390  								Name: "baz[a]",
  2391  								Old:  "1",
  2392  								New:  "",
  2393  							},
  2394  							{
  2395  								Type: DiffTypeEdited,
  2396  								Name: "baz[b]",
  2397  								Old:  "2",
  2398  								New:  "3",
  2399  							},
  2400  							{
  2401  								Type: DiffTypeAdded,
  2402  								Name: "baz[c]",
  2403  								Old:  "",
  2404  								New:  "4",
  2405  							},
  2406  							{
  2407  								Type: DiffTypeEdited,
  2408  								Name: "boom.Label",
  2409  								Old:  "boom_port",
  2410  								New:  "boom_port2",
  2411  							},
  2412  							{
  2413  								Type: DiffTypeEdited,
  2414  								Name: "foo",
  2415  								Old:  "1",
  2416  								New:  "2",
  2417  							},
  2418  						},
  2419  					},
  2420  				},
  2421  			},
  2422  		},
  2423  		{
  2424  			// Config edited with context
  2425  			Contextual: true,
  2426  			Old: &Task{
  2427  				Config: map[string]interface{}{
  2428  					"foo": 1,
  2429  					"bar": "baz",
  2430  					"bam": []string{"a", "b"},
  2431  					"baz": map[string]int{
  2432  						"a": 1,
  2433  						"b": 2,
  2434  					},
  2435  					"boom": &Port{
  2436  						Label: "boom_port",
  2437  					},
  2438  				},
  2439  			},
  2440  			New: &Task{
  2441  				Config: map[string]interface{}{
  2442  					"foo": 2,
  2443  					"bar": "baz",
  2444  					"bam": []string{"a", "c", "d"},
  2445  					"baz": map[string]int{
  2446  						"a": 1,
  2447  						"b": 2,
  2448  					},
  2449  					"boom": &Port{
  2450  						Label: "boom_port",
  2451  					},
  2452  				},
  2453  			},
  2454  			Expected: &TaskDiff{
  2455  				Type: DiffTypeEdited,
  2456  				Objects: []*ObjectDiff{
  2457  					{
  2458  						Type: DiffTypeEdited,
  2459  						Name: "Config",
  2460  						Fields: []*FieldDiff{
  2461  							{
  2462  								Type: DiffTypeNone,
  2463  								Name: "bam[0]",
  2464  								Old:  "a",
  2465  								New:  "a",
  2466  							},
  2467  							{
  2468  								Type: DiffTypeEdited,
  2469  								Name: "bam[1]",
  2470  								Old:  "b",
  2471  								New:  "c",
  2472  							},
  2473  							{
  2474  								Type: DiffTypeAdded,
  2475  								Name: "bam[2]",
  2476  								Old:  "",
  2477  								New:  "d",
  2478  							},
  2479  							{
  2480  								Type: DiffTypeNone,
  2481  								Name: "bar",
  2482  								Old:  "baz",
  2483  								New:  "baz",
  2484  							},
  2485  							{
  2486  								Type: DiffTypeNone,
  2487  								Name: "baz[a]",
  2488  								Old:  "1",
  2489  								New:  "1",
  2490  							},
  2491  							{
  2492  								Type: DiffTypeNone,
  2493  								Name: "baz[b]",
  2494  								Old:  "2",
  2495  								New:  "2",
  2496  							},
  2497  							{
  2498  								Type: DiffTypeNone,
  2499  								Name: "boom.Label",
  2500  								Old:  "boom_port",
  2501  								New:  "boom_port",
  2502  							},
  2503  							{
  2504  								Type: DiffTypeNone,
  2505  								Name: "boom.Value",
  2506  								Old:  "0",
  2507  								New:  "0",
  2508  							},
  2509  							{
  2510  								Type: DiffTypeEdited,
  2511  								Name: "foo",
  2512  								Old:  "1",
  2513  								New:  "2",
  2514  							},
  2515  						},
  2516  					},
  2517  				},
  2518  			},
  2519  		},
  2520  		{
  2521  			// Services edited (no checks)
  2522  			Old: &Task{
  2523  				Services: []*Service{
  2524  					{
  2525  						Name:      "foo",
  2526  						PortLabel: "foo",
  2527  					},
  2528  					{
  2529  						Name:      "bar",
  2530  						PortLabel: "bar",
  2531  					},
  2532  					{
  2533  						Name:      "baz",
  2534  						PortLabel: "baz",
  2535  					},
  2536  				},
  2537  			},
  2538  			New: &Task{
  2539  				Services: []*Service{
  2540  					{
  2541  						Name:      "bar",
  2542  						PortLabel: "bar",
  2543  					},
  2544  					{
  2545  						Name:      "baz",
  2546  						PortLabel: "baz2",
  2547  					},
  2548  					{
  2549  						Name:      "bam",
  2550  						PortLabel: "bam",
  2551  					},
  2552  				},
  2553  			},
  2554  			Expected: &TaskDiff{
  2555  				Type: DiffTypeEdited,
  2556  				Objects: []*ObjectDiff{
  2557  					{
  2558  						Type: DiffTypeEdited,
  2559  						Name: "Service",
  2560  						Fields: []*FieldDiff{
  2561  							{
  2562  								Type: DiffTypeEdited,
  2563  								Name: "PortLabel",
  2564  								Old:  "baz",
  2565  								New:  "baz2",
  2566  							},
  2567  						},
  2568  					},
  2569  					{
  2570  						Type: DiffTypeAdded,
  2571  						Name: "Service",
  2572  						Fields: []*FieldDiff{
  2573  							{
  2574  								Type: DiffTypeAdded,
  2575  								Name: "Name",
  2576  								Old:  "",
  2577  								New:  "bam",
  2578  							},
  2579  							{
  2580  								Type: DiffTypeAdded,
  2581  								Name: "PortLabel",
  2582  								Old:  "",
  2583  								New:  "bam",
  2584  							},
  2585  						},
  2586  					},
  2587  					{
  2588  						Type: DiffTypeDeleted,
  2589  						Name: "Service",
  2590  						Fields: []*FieldDiff{
  2591  							{
  2592  								Type: DiffTypeDeleted,
  2593  								Name: "Name",
  2594  								Old:  "foo",
  2595  								New:  "",
  2596  							},
  2597  							{
  2598  								Type: DiffTypeDeleted,
  2599  								Name: "PortLabel",
  2600  								Old:  "foo",
  2601  								New:  "",
  2602  							},
  2603  						},
  2604  					},
  2605  				},
  2606  			},
  2607  		},
  2608  		{
  2609  			// Services edited (no checks) with context
  2610  			Contextual: true,
  2611  			Old: &Task{
  2612  				Services: []*Service{
  2613  					{
  2614  						Name:      "foo",
  2615  						PortLabel: "foo",
  2616  					},
  2617  				},
  2618  			},
  2619  			New: &Task{
  2620  				Services: []*Service{
  2621  					{
  2622  						Name:      "foo",
  2623  						PortLabel: "bar",
  2624  					},
  2625  				},
  2626  			},
  2627  			Expected: &TaskDiff{
  2628  				Type: DiffTypeEdited,
  2629  				Objects: []*ObjectDiff{
  2630  					{
  2631  						Type: DiffTypeEdited,
  2632  						Name: "Service",
  2633  						Fields: []*FieldDiff{
  2634  							{
  2635  								Type: DiffTypeNone,
  2636  								Name: "Name",
  2637  								Old:  "foo",
  2638  								New:  "foo",
  2639  							},
  2640  							{
  2641  								Type: DiffTypeEdited,
  2642  								Name: "PortLabel",
  2643  								Old:  "foo",
  2644  								New:  "bar",
  2645  							},
  2646  						},
  2647  					},
  2648  				},
  2649  			},
  2650  		},
  2651  		{
  2652  			// Service Checks edited
  2653  			Old: &Task{
  2654  				Services: []*Service{
  2655  					{
  2656  						Name: "foo",
  2657  						Checks: []*ServiceCheck{
  2658  							{
  2659  								Name:     "foo",
  2660  								Type:     "http",
  2661  								Command:  "foo",
  2662  								Args:     []string{"foo"},
  2663  								Path:     "foo",
  2664  								Protocol: "http",
  2665  								Interval: 1 * time.Second,
  2666  								Timeout:  1 * time.Second,
  2667  							},
  2668  							{
  2669  								Name:     "bar",
  2670  								Type:     "http",
  2671  								Command:  "foo",
  2672  								Args:     []string{"foo"},
  2673  								Path:     "foo",
  2674  								Protocol: "http",
  2675  								Interval: 1 * time.Second,
  2676  								Timeout:  1 * time.Second,
  2677  							},
  2678  							{
  2679  								Name:     "baz",
  2680  								Type:     "http",
  2681  								Command:  "foo",
  2682  								Args:     []string{"foo"},
  2683  								Path:     "foo",
  2684  								Protocol: "http",
  2685  								Interval: 1 * time.Second,
  2686  								Timeout:  1 * time.Second,
  2687  							},
  2688  						},
  2689  					},
  2690  				},
  2691  			},
  2692  			New: &Task{
  2693  				Services: []*Service{
  2694  					{
  2695  						Name: "foo",
  2696  						Checks: []*ServiceCheck{
  2697  							{
  2698  								Name:     "bar",
  2699  								Type:     "http",
  2700  								Command:  "foo",
  2701  								Args:     []string{"foo"},
  2702  								Path:     "foo",
  2703  								Protocol: "http",
  2704  								Interval: 1 * time.Second,
  2705  								Timeout:  1 * time.Second,
  2706  							},
  2707  							{
  2708  								Name:     "baz",
  2709  								Type:     "tcp",
  2710  								Command:  "foo",
  2711  								Args:     []string{"foo"},
  2712  								Path:     "foo",
  2713  								Protocol: "http",
  2714  								Interval: 1 * time.Second,
  2715  								Timeout:  1 * time.Second,
  2716  							},
  2717  							{
  2718  								Name:     "bam",
  2719  								Type:     "http",
  2720  								Command:  "foo",
  2721  								Args:     []string{"foo"},
  2722  								Path:     "foo",
  2723  								Protocol: "http",
  2724  								Interval: 1 * time.Second,
  2725  								Timeout:  1 * time.Second,
  2726  							},
  2727  						},
  2728  					},
  2729  				},
  2730  			},
  2731  			Expected: &TaskDiff{
  2732  				Type: DiffTypeEdited,
  2733  				Objects: []*ObjectDiff{
  2734  					{
  2735  						Type: DiffTypeEdited,
  2736  						Name: "Service",
  2737  						Objects: []*ObjectDiff{
  2738  							{
  2739  								Type: DiffTypeEdited,
  2740  								Name: "Check",
  2741  								Fields: []*FieldDiff{
  2742  									{
  2743  										Type: DiffTypeEdited,
  2744  										Name: "Type",
  2745  										Old:  "http",
  2746  										New:  "tcp",
  2747  									},
  2748  								},
  2749  							},
  2750  							{
  2751  								Type: DiffTypeAdded,
  2752  								Name: "Check",
  2753  								Fields: []*FieldDiff{
  2754  									{
  2755  										Type: DiffTypeAdded,
  2756  										Name: "Command",
  2757  										Old:  "",
  2758  										New:  "foo",
  2759  									},
  2760  									{
  2761  										Type: DiffTypeAdded,
  2762  										Name: "Interval",
  2763  										Old:  "",
  2764  										New:  "1000000000",
  2765  									},
  2766  									{
  2767  										Type: DiffTypeAdded,
  2768  										Name: "Name",
  2769  										Old:  "",
  2770  										New:  "bam",
  2771  									},
  2772  									{
  2773  										Type: DiffTypeAdded,
  2774  										Name: "Path",
  2775  										Old:  "",
  2776  										New:  "foo",
  2777  									},
  2778  									{
  2779  										Type: DiffTypeAdded,
  2780  										Name: "Protocol",
  2781  										Old:  "",
  2782  										New:  "http",
  2783  									},
  2784  									{
  2785  										Type: DiffTypeAdded,
  2786  										Name: "Timeout",
  2787  										Old:  "",
  2788  										New:  "1000000000",
  2789  									},
  2790  									{
  2791  										Type: DiffTypeAdded,
  2792  										Name: "Type",
  2793  										Old:  "",
  2794  										New:  "http",
  2795  									},
  2796  								},
  2797  							},
  2798  							{
  2799  								Type: DiffTypeDeleted,
  2800  								Name: "Check",
  2801  								Fields: []*FieldDiff{
  2802  									{
  2803  										Type: DiffTypeDeleted,
  2804  										Name: "Command",
  2805  										Old:  "foo",
  2806  										New:  "",
  2807  									},
  2808  									{
  2809  										Type: DiffTypeDeleted,
  2810  										Name: "Interval",
  2811  										Old:  "1000000000",
  2812  										New:  "",
  2813  									},
  2814  									{
  2815  										Type: DiffTypeDeleted,
  2816  										Name: "Name",
  2817  										Old:  "foo",
  2818  										New:  "",
  2819  									},
  2820  									{
  2821  										Type: DiffTypeDeleted,
  2822  										Name: "Path",
  2823  										Old:  "foo",
  2824  										New:  "",
  2825  									},
  2826  									{
  2827  										Type: DiffTypeDeleted,
  2828  										Name: "Protocol",
  2829  										Old:  "http",
  2830  										New:  "",
  2831  									},
  2832  									{
  2833  										Type: DiffTypeDeleted,
  2834  										Name: "Timeout",
  2835  										Old:  "1000000000",
  2836  										New:  "",
  2837  									},
  2838  									{
  2839  										Type: DiffTypeDeleted,
  2840  										Name: "Type",
  2841  										Old:  "http",
  2842  										New:  "",
  2843  									},
  2844  								},
  2845  							},
  2846  						},
  2847  					},
  2848  				},
  2849  			},
  2850  		},
  2851  		{
  2852  			// Service Checks edited with context
  2853  			Contextual: true,
  2854  			Old: &Task{
  2855  				Services: []*Service{
  2856  					{
  2857  						Name: "foo",
  2858  						Checks: []*ServiceCheck{
  2859  							{
  2860  								Name:          "foo",
  2861  								Type:          "http",
  2862  								Command:       "foo",
  2863  								Args:          []string{"foo"},
  2864  								Path:          "foo",
  2865  								Protocol:      "http",
  2866  								Interval:      1 * time.Second,
  2867  								Timeout:       1 * time.Second,
  2868  								InitialStatus: "critical",
  2869  							},
  2870  						},
  2871  					},
  2872  				},
  2873  			},
  2874  			New: &Task{
  2875  				Services: []*Service{
  2876  					{
  2877  						Name: "foo",
  2878  						Checks: []*ServiceCheck{
  2879  							{
  2880  								Name:          "foo",
  2881  								Type:          "tcp",
  2882  								Command:       "foo",
  2883  								Args:          []string{"foo"},
  2884  								Path:          "foo",
  2885  								Protocol:      "http",
  2886  								Interval:      1 * time.Second,
  2887  								Timeout:       1 * time.Second,
  2888  								InitialStatus: "passing",
  2889  							},
  2890  						},
  2891  					},
  2892  				},
  2893  			},
  2894  			Expected: &TaskDiff{
  2895  				Type: DiffTypeEdited,
  2896  				Objects: []*ObjectDiff{
  2897  					{
  2898  						Type: DiffTypeEdited,
  2899  						Name: "Service",
  2900  						Fields: []*FieldDiff{
  2901  							{
  2902  								Type: DiffTypeNone,
  2903  								Name: "Name",
  2904  								Old:  "foo",
  2905  								New:  "foo",
  2906  							},
  2907  							{
  2908  								Type: DiffTypeNone,
  2909  								Name: "PortLabel",
  2910  								Old:  "",
  2911  								New:  "",
  2912  							},
  2913  						},
  2914  						Objects: []*ObjectDiff{
  2915  							{
  2916  								Type: DiffTypeEdited,
  2917  								Name: "Check",
  2918  								Fields: []*FieldDiff{
  2919  									{
  2920  										Type: DiffTypeNone,
  2921  										Name: "Command",
  2922  										Old:  "foo",
  2923  										New:  "foo",
  2924  									},
  2925  									{
  2926  										Type: DiffTypeEdited,
  2927  										Name: "InitialStatus",
  2928  										Old:  "critical",
  2929  										New:  "passing",
  2930  									},
  2931  									{
  2932  										Type: DiffTypeNone,
  2933  										Name: "Interval",
  2934  										Old:  "1000000000",
  2935  										New:  "1000000000",
  2936  									},
  2937  									{
  2938  										Type: DiffTypeNone,
  2939  										Name: "Name",
  2940  										Old:  "foo",
  2941  										New:  "foo",
  2942  									},
  2943  									{
  2944  										Type: DiffTypeNone,
  2945  										Name: "Path",
  2946  										Old:  "foo",
  2947  										New:  "foo",
  2948  									},
  2949  									{
  2950  										Type: DiffTypeNone,
  2951  										Name: "PortLabel",
  2952  										Old:  "",
  2953  										New:  "",
  2954  									},
  2955  									{
  2956  										Type: DiffTypeNone,
  2957  										Name: "Protocol",
  2958  										Old:  "http",
  2959  										New:  "http",
  2960  									},
  2961  									{
  2962  										Type: DiffTypeNone,
  2963  										Name: "Timeout",
  2964  										Old:  "1000000000",
  2965  										New:  "1000000000",
  2966  									},
  2967  									{
  2968  										Type: DiffTypeEdited,
  2969  										Name: "Type",
  2970  										Old:  "http",
  2971  										New:  "tcp",
  2972  									},
  2973  								},
  2974  							},
  2975  						},
  2976  					},
  2977  				},
  2978  			},
  2979  		},
  2980  	}
  2981  
  2982  	for i, c := range cases {
  2983  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2984  		if c.Error && err == nil {
  2985  			t.Fatalf("case %d: expected errored", i+1)
  2986  		} else if err != nil {
  2987  			if !c.Error {
  2988  				t.Fatalf("case %d: errored %#v", i+1, err)
  2989  			} else {
  2990  				continue
  2991  			}
  2992  		}
  2993  
  2994  		if !reflect.DeepEqual(actual, c.Expected) {
  2995  			t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  2996  				i+1, actual, c.Expected)
  2997  		}
  2998  	}
  2999  }