github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/scheduler/algorithm/table_helpers_test.go (about)

     1  package algorithm_test
     2  
     3  import (
     4  	"compress/gzip"
     5  	"context"
     6  	"crypto/sha256"
     7  	"database/sql"
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  	"strconv"
    12  	"time"
    13  
    14  	sq "github.com/Masterminds/squirrel"
    15  	"github.com/pf-qiu/concourse/v6/atc"
    16  	"github.com/pf-qiu/concourse/v6/atc/db"
    17  	"github.com/pf-qiu/concourse/v6/atc/scheduler/algorithm"
    18  	"github.com/pf-qiu/concourse/v6/tracing"
    19  	"github.com/lib/pq"
    20  	"github.com/onsi/ginkgo"
    21  	. "github.com/onsi/gomega"
    22  	gocache "github.com/patrickmn/go-cache"
    23  	"go.opentelemetry.io/otel/label"
    24  )
    25  
    26  type DB struct {
    27  	BuildInputs      []DBRow
    28  	BuildOutputs     []DBRow
    29  	BuildPipes       []DBRow
    30  	Resources        []DBRow
    31  	NeedsV6Migration bool
    32  }
    33  
    34  type DBRow struct {
    35  	Job                   string
    36  	BuildID               int
    37  	Resource              string
    38  	Version               string
    39  	CheckOrder            int
    40  	VersionID             int
    41  	Disabled              bool
    42  	FromBuildID           int
    43  	ToBuildID             int
    44  	RerunOfBuildID        int
    45  	BuildStatus           string
    46  	NoResourceConfigScope bool
    47  	DoNotInsertVersion    bool
    48  }
    49  
    50  type Example struct {
    51  	LoadDB     string
    52  	DB         DB
    53  	Inputs     Inputs
    54  	Result     Result
    55  	Iterations int
    56  	Error      error
    57  }
    58  
    59  type Inputs []Input
    60  
    61  type Input struct {
    62  	Name                  string
    63  	Resource              string
    64  	Passed                []string
    65  	Version               Version
    66  	NoResourceConfigScope bool
    67  }
    68  
    69  type Version struct {
    70  	Every  bool
    71  	Latest bool
    72  	Pinned string
    73  }
    74  
    75  type Result struct {
    76  	OK               bool
    77  	Values           map[string]string
    78  	PassedBuildIDs   map[string][]int
    79  	Errors           map[string]string
    80  	ExpectedMigrated map[int]map[int][]string
    81  	HasNext          bool
    82  	NoNext           bool
    83  }
    84  
    85  type StringMapping map[string]int
    86  
    87  func (mapping StringMapping) ID(str string) int {
    88  	id, found := mapping[str]
    89  	if !found {
    90  		id = len(mapping) + 1
    91  		mapping[str] = id
    92  	}
    93  
    94  	return id
    95  }
    96  
    97  func (mapping StringMapping) Name(id int) string {
    98  	for mappingName, mappingID := range mapping {
    99  		if id == mappingID {
   100  			return mappingName
   101  		}
   102  	}
   103  
   104  	panic(fmt.Sprintf("no name found for %d", id))
   105  }
   106  
   107  const CurrentJobName = "current"
   108  
   109  func (example Example) Run() {
   110  	currentTest := ginkgo.CurrentGinkgoTestDescription()
   111  
   112  	ctx, span := tracing.StartSpan(context.Background(), currentTest.TestText, tracing.Attrs{})
   113  	defer span.End()
   114  
   115  	setup := setupDB{
   116  		teamID:      1,
   117  		pipelineID:  1,
   118  		psql:        sq.StatementBuilder.PlaceholderFormat(sq.Dollar).RunWith(dbConn),
   119  		jobIDs:      StringMapping{},
   120  		resourceIDs: StringMapping{},
   121  		versionIDs:  StringMapping{},
   122  	}
   123  
   124  	team, err := teamFactory.CreateTeam(atc.Team{Name: "algorithm"})
   125  	Expect(err).NotTo(HaveOccurred())
   126  
   127  	pipeline, _, err := team.SavePipeline(atc.PipelineRef{Name: "algorithm"}, atc.Config{}, db.ConfigVersion(0), false)
   128  	Expect(err).NotTo(HaveOccurred())
   129  
   130  	setupTx, err := dbConn.Begin()
   131  	Expect(err).ToNot(HaveOccurred())
   132  
   133  	brt := db.BaseResourceType{
   134  		Name: "some-base-type",
   135  	}
   136  
   137  	_, err = brt.FindOrCreate(setupTx, false)
   138  	Expect(err).NotTo(HaveOccurred())
   139  	Expect(setupTx.Commit()).To(Succeed())
   140  
   141  	resources := map[string]atc.ResourceConfig{}
   142  
   143  	cache := gocache.New(10*time.Second, 10*time.Second)
   144  
   145  	var versionsDB db.VersionsDB
   146  	if example.LoadDB != "" {
   147  		if os.Getenv("ALGORITHM_REGRESSION") == "" {
   148  			ginkgo.Skip("skipping; to run, set $ALGORITHM_REGRESSION")
   149  		}
   150  
   151  		versionsDB = example.importVersionsDB(ctx, setup, cache, resources)
   152  	} else {
   153  		versionsDB = example.setupVersionsDB(ctx, setup, cache, resources)
   154  	}
   155  
   156  	inputConfigs := make(db.InputConfigs, len(example.Inputs))
   157  	for i, input := range example.Inputs {
   158  		passed := db.JobSet{}
   159  		for _, jobName := range input.Passed {
   160  			setup.insertJob(jobName)
   161  			passed[setup.jobIDs.ID(jobName)] = true
   162  		}
   163  
   164  		inputConfigs[i] = db.InputConfig{
   165  			Name:            input.Name,
   166  			Passed:          passed,
   167  			ResourceID:      setup.resourceIDs.ID(input.Resource),
   168  			UseEveryVersion: input.Version.Every,
   169  			JobID:           setup.jobIDs.ID(CurrentJobName),
   170  		}
   171  
   172  		if len(input.Version.Pinned) != 0 {
   173  			inputConfigs[i].PinnedVersion = atc.Version{"ver": input.Version.Pinned}
   174  
   175  			setup.insertPinned(setup.resourceIDs.ID(input.Resource), atc.Version{"ver": input.Version.Pinned})
   176  		}
   177  	}
   178  
   179  	setup.insertJob("current")
   180  
   181  	job, found, err := pipeline.Job("current")
   182  	Expect(err).ToNot(HaveOccurred())
   183  	Expect(found).To(BeTrue())
   184  
   185  	alg := algorithm.New(versionsDB)
   186  
   187  	iterations := 1
   188  	if example.Iterations != 0 {
   189  		iterations = example.Iterations
   190  	}
   191  
   192  	for i := 0; i < iterations; i++ {
   193  		example.assert(ctx, setup, alg, job, inputConfigs)
   194  		cache.Flush()
   195  	}
   196  }
   197  
   198  func (example Example) importVersionsDB(ctx context.Context, setup setupDB, cache *gocache.Cache, resources map[string]atc.ResourceConfig) db.VersionsDB {
   199  	ctx, span := tracing.StartSpan(ctx, "importVersionsDB", tracing.Attrs{
   200  		"db": example.LoadDB,
   201  	})
   202  	defer span.End()
   203  
   204  	versionsDB := db.NewVersionsDB(dbConn, 100, cache)
   205  
   206  	dbFile, err := os.Open(example.LoadDB)
   207  	Expect(err).ToNot(HaveOccurred())
   208  
   209  	gr, err := gzip.NewReader(dbFile)
   210  	Expect(err).ToNot(HaveOccurred())
   211  
   212  	var debugDB atc.DebugVersionsDB
   213  	err = func(ctx context.Context) error {
   214  		ctx, span = tracing.StartSpan(ctx, "Decode", tracing.Attrs{})
   215  		defer span.End()
   216  		return json.NewDecoder(gr).Decode(&debugDB)
   217  	}(ctx)
   218  	Expect(err).ToNot(HaveOccurred())
   219  
   220  	span.AddEvent(
   221  		ctx,
   222  		"decoded",
   223  		label.Int("Jobs", len(debugDB.Jobs)),
   224  		label.Int("Resources", len(debugDB.Resources)),
   225  		label.Int("LegacyJobIDs", len(debugDB.LegacyJobIDs)),
   226  		label.Int("LegacyResourceIDs", len(debugDB.LegacyResourceIDs)),
   227  		label.Int("ResourceVersions", len(debugDB.ResourceVersions)),
   228  		label.Int("BuildInputs", len(debugDB.BuildInputs)),
   229  		label.Int("BuildOutputs", len(debugDB.BuildOutputs)),
   230  		label.Int("BuildReruns", len(debugDB.BuildReruns)),
   231  	)
   232  
   233  	// legacy, pre-6.0
   234  	for name, id := range debugDB.LegacyJobIDs {
   235  		setup.jobIDs[name] = id
   236  		setup.insertJob(name)
   237  	}
   238  
   239  	for name, id := range debugDB.LegacyResourceIDs {
   240  		setup.resourceIDs[name] = id
   241  
   242  		setup.insertResource(name, &id)
   243  		resources[name] = atc.ResourceConfig{
   244  			Name: name,
   245  			Type: "some-base-type",
   246  			Source: atc.Source{
   247  				name: "source",
   248  			},
   249  		}
   250  	}
   251  
   252  	// 6.0+
   253  	for _, job := range debugDB.Jobs {
   254  		setup.jobIDs[job.Name] = job.ID
   255  		setup.insertJob(job.Name)
   256  	}
   257  
   258  	for _, resource := range debugDB.Resources {
   259  		setup.resourceIDs[resource.Name] = resource.ID
   260  
   261  		setup.insertResource(resource.Name, resource.ScopeID)
   262  		resources[resource.Name] = atc.ResourceConfig{
   263  			Name: resource.Name,
   264  			Type: "some-base-type",
   265  			Source: atc.Source{
   266  				resource.Name: "source",
   267  			},
   268  		}
   269  	}
   270  
   271  	err = func(ctx context.Context) error {
   272  		ctx, span = tracing.StartSpan(ctx, "import versions", tracing.Attrs{})
   273  		defer span.End()
   274  
   275  		tx, err := dbConn.Begin()
   276  		Expect(err).ToNot(HaveOccurred())
   277  
   278  		stmt, err := tx.Prepare(pq.CopyIn("resource_config_versions", "id", "resource_config_scope_id", "version", "version_md5", "check_order"))
   279  		Expect(err).ToNot(HaveOccurred())
   280  
   281  		for _, row := range debugDB.ResourceVersions {
   282  			name := fmt.Sprintf("imported-r%dv%d", row.ResourceID, row.VersionID)
   283  			setup.versionIDs[name] = row.VersionID
   284  
   285  			scope := row.ScopeID
   286  			if scope == 0 {
   287  				// pre-6.0
   288  				scope = row.ResourceID
   289  			}
   290  
   291  			_, err := stmt.Exec(row.VersionID, scope, "{}", strconv.Itoa(row.VersionID), row.CheckOrder)
   292  			Expect(err).ToNot(HaveOccurred())
   293  		}
   294  
   295  		_, err = stmt.Exec()
   296  		Expect(err).ToNot(HaveOccurred())
   297  
   298  		err = stmt.Close()
   299  		Expect(err).ToNot(HaveOccurred())
   300  
   301  		err = tx.Commit()
   302  		Expect(err).ToNot(HaveOccurred())
   303  
   304  		return nil
   305  	}(ctx)
   306  	Expect(err).ToNot(HaveOccurred())
   307  
   308  	err = func(ctx context.Context) error {
   309  		ctx, span = tracing.StartSpan(ctx, "import builds", tracing.Attrs{})
   310  		defer span.End()
   311  
   312  		tx, err := dbConn.Begin()
   313  		Expect(err).ToNot(HaveOccurred())
   314  
   315  		stmt, err := tx.Prepare(pq.CopyIn("builds", "team_id", "id", "job_id", "name", "status"))
   316  		Expect(err).ToNot(HaveOccurred())
   317  
   318  		imported := map[int]bool{}
   319  
   320  		for _, row := range debugDB.BuildOutputs {
   321  			if imported[row.BuildID] {
   322  				continue
   323  			}
   324  
   325  			_, err := stmt.Exec(setup.teamID, row.BuildID, row.JobID, row.BuildID, "succeeded")
   326  			Expect(err).ToNot(HaveOccurred())
   327  
   328  			imported[row.BuildID] = true
   329  		}
   330  
   331  		for _, row := range debugDB.BuildInputs {
   332  			if imported[row.BuildID] {
   333  				continue
   334  			}
   335  
   336  			// any builds not created at this point must have failed as they weren't
   337  			// present via outputs
   338  			_, err := stmt.Exec(setup.teamID, row.BuildID, row.JobID, row.BuildID, "failed")
   339  			Expect(err).ToNot(HaveOccurred())
   340  
   341  			imported[row.BuildID] = true
   342  		}
   343  
   344  		for _, row := range debugDB.BuildReruns {
   345  			if imported[row.RerunOf] {
   346  				continue
   347  			}
   348  
   349  			// any builds not created at this point must have failed as they weren't
   350  			// present via outputs
   351  			_, err := stmt.Exec(setup.teamID, row.RerunOf, row.JobID, "some-name", "failed")
   352  			Expect(err).ToNot(HaveOccurred())
   353  
   354  			imported[row.RerunOf] = true
   355  		}
   356  
   357  		_, err = stmt.Exec()
   358  		Expect(err).ToNot(HaveOccurred())
   359  
   360  		err = stmt.Close()
   361  		Expect(err).ToNot(HaveOccurred())
   362  
   363  		err = tx.Commit()
   364  		Expect(err).ToNot(HaveOccurred())
   365  
   366  		for _, row := range debugDB.BuildReruns {
   367  			if !imported[row.BuildID] {
   368  				// probably a build we don't care about
   369  				continue
   370  			}
   371  
   372  			_, err = setup.psql.Update("builds").
   373  				Set("rerun_of", row.RerunOf).
   374  				Where(sq.Eq{"id": row.BuildID}).
   375  				Exec()
   376  			Expect(err).ToNot(HaveOccurred())
   377  		}
   378  
   379  		return nil
   380  	}(ctx)
   381  	Expect(err).ToNot(HaveOccurred())
   382  
   383  	err = func(ctx context.Context) error {
   384  		ctx, span = tracing.StartSpan(ctx, "import inputs", tracing.Attrs{})
   385  		defer span.End()
   386  
   387  		tx, err := dbConn.Begin()
   388  		Expect(err).ToNot(HaveOccurred())
   389  
   390  		stmt, err := tx.Prepare(pq.CopyIn("build_resource_config_version_inputs", "build_id", "resource_id", "version_md5", "name", "first_occurrence"))
   391  		Expect(err).ToNot(HaveOccurred())
   392  
   393  		for i, row := range debugDB.BuildInputs {
   394  			_, err := stmt.Exec(row.BuildID, row.ResourceID, strconv.Itoa(row.VersionID), strconv.Itoa(i), false)
   395  			Expect(err).ToNot(HaveOccurred())
   396  		}
   397  
   398  		_, err = stmt.Exec()
   399  		Expect(err).ToNot(HaveOccurred())
   400  
   401  		err = stmt.Close()
   402  		Expect(err).ToNot(HaveOccurred())
   403  
   404  		err = tx.Commit()
   405  		Expect(err).ToNot(HaveOccurred())
   406  
   407  		return nil
   408  	}(ctx)
   409  	Expect(err).ToNot(HaveOccurred())
   410  
   411  	err = func(ctx context.Context) error {
   412  		ctx, span = tracing.StartSpan(ctx, "import outputs", tracing.Attrs{})
   413  		defer span.End()
   414  
   415  		tx, err := dbConn.Begin()
   416  		Expect(err).ToNot(HaveOccurred())
   417  
   418  		stmt, err := tx.Prepare(pq.CopyIn("build_resource_config_version_outputs", "build_id", "resource_id", "version_md5", "name"))
   419  		Expect(err).ToNot(HaveOccurred())
   420  
   421  		for i, row := range debugDB.BuildOutputs {
   422  			_, err := stmt.Exec(row.BuildID, row.ResourceID, strconv.Itoa(row.VersionID), strconv.Itoa(i))
   423  			Expect(err).ToNot(HaveOccurred())
   424  		}
   425  
   426  		_, err = stmt.Exec()
   427  		Expect(err).ToNot(HaveOccurred())
   428  
   429  		err = stmt.Close()
   430  		Expect(err).ToNot(HaveOccurred())
   431  
   432  		err = tx.Commit()
   433  		Expect(err).ToNot(HaveOccurred())
   434  
   435  		return nil
   436  	}(ctx)
   437  	Expect(err).ToNot(HaveOccurred())
   438  
   439  	return versionsDB
   440  }
   441  
   442  func (example Example) setupVersionsDB(ctx context.Context, setup setupDB, cache *gocache.Cache, resources map[string]atc.ResourceConfig) db.VersionsDB {
   443  	ctx, span := tracing.StartSpan(ctx, "setupVersionsDB", tracing.Attrs{})
   444  	defer span.End()
   445  
   446  	versionsDB := db.NewVersionsDB(dbConn, 2, cache)
   447  
   448  	for _, row := range example.DB.Resources {
   449  		setup.insertRowVersion(resources, row)
   450  	}
   451  
   452  	buildOutputs := map[int]map[string][]string{}
   453  	buildToJobID := map[int]int{}
   454  	buildToRerunOf := map[int]int{}
   455  	for _, row := range example.DB.BuildOutputs {
   456  		setup.insertRowVersion(resources, row)
   457  		setup.insertRowBuild(row, example.DB.NeedsV6Migration)
   458  
   459  		resourceID := setup.resourceIDs.ID(row.Resource)
   460  
   461  		versionJSON, err := json.Marshal(atc.Version{"ver": row.Version})
   462  		Expect(err).ToNot(HaveOccurred())
   463  
   464  		_, err = setup.psql.Insert("build_resource_config_version_outputs").
   465  			Columns("build_id", "resource_id", "version_md5", "name").
   466  			Values(row.BuildID, resourceID, sq.Expr("md5(?)", versionJSON), row.Resource).
   467  			Exec()
   468  		Expect(err).ToNot(HaveOccurred())
   469  
   470  		if !example.DB.NeedsV6Migration {
   471  			outputs, ok := buildOutputs[row.BuildID]
   472  			if !ok {
   473  				outputs = map[string][]string{}
   474  				buildOutputs[row.BuildID] = outputs
   475  			}
   476  
   477  			key := strconv.Itoa(resourceID)
   478  
   479  			outputs[key] = append(outputs[key], convertToMD5(row.Version))
   480  			buildToJobID[row.BuildID] = setup.jobIDs.ID(row.Job)
   481  
   482  			if row.RerunOfBuildID != 0 {
   483  				buildToRerunOf[row.BuildID] = row.RerunOfBuildID
   484  			}
   485  		}
   486  	}
   487  
   488  	for _, row := range example.DB.BuildInputs {
   489  		setup.insertRowVersion(resources, row)
   490  		setup.insertRowBuild(row, example.DB.NeedsV6Migration)
   491  
   492  		resourceID := setup.resourceIDs.ID(row.Resource)
   493  
   494  		versionJSON, err := json.Marshal(atc.Version{"ver": row.Version})
   495  		Expect(err).ToNot(HaveOccurred())
   496  
   497  		_, err = setup.psql.Insert("build_resource_config_version_inputs").
   498  			Columns("build_id", "resource_id", "version_md5", "name", "first_occurrence").
   499  			Values(row.BuildID, resourceID, sq.Expr("md5(?)", versionJSON), row.Resource, false).
   500  			Exec()
   501  		Expect(err).ToNot(HaveOccurred())
   502  
   503  		if !example.DB.NeedsV6Migration {
   504  			outputs, ok := buildOutputs[row.BuildID]
   505  			if !ok {
   506  				outputs = map[string][]string{}
   507  				buildOutputs[row.BuildID] = outputs
   508  			}
   509  
   510  			key := strconv.Itoa(resourceID)
   511  
   512  			outputs[key] = append(outputs[key], convertToMD5(row.Version))
   513  			buildToJobID[row.BuildID] = setup.jobIDs.ID(row.Job)
   514  
   515  			if row.RerunOfBuildID != 0 {
   516  				buildToRerunOf[row.BuildID] = row.RerunOfBuildID
   517  			}
   518  		}
   519  	}
   520  
   521  	for buildID, outputs := range buildOutputs {
   522  		outputsJSON, err := json.Marshal(outputs)
   523  		Expect(err).ToNot(HaveOccurred())
   524  
   525  		var rerunOf sql.NullInt64
   526  		if buildToRerunOf[buildID] != 0 {
   527  			rerunOf.Int64 = int64(buildToRerunOf[buildID])
   528  		}
   529  
   530  		_, err = setup.psql.Insert("successful_build_outputs").
   531  			Columns("build_id", "job_id", "rerun_of", "outputs").
   532  			Values(buildID, buildToJobID[buildID], rerunOf, outputsJSON).
   533  			Suffix("ON CONFLICT DO NOTHING").
   534  			Exec()
   535  		Expect(err).ToNot(HaveOccurred())
   536  	}
   537  
   538  	for _, row := range example.DB.BuildPipes {
   539  		setup.insertBuildPipe(row)
   540  	}
   541  
   542  	for _, input := range example.Inputs {
   543  		resourceID := setup.resourceIDs.ID(input.Resource)
   544  
   545  		var scope *int
   546  		if !input.NoResourceConfigScope {
   547  			scope = &resourceID
   548  		}
   549  
   550  		setup.insertResource(input.Resource, scope)
   551  
   552  		resources[input.Resource] = atc.ResourceConfig{
   553  			Name: input.Resource,
   554  			Type: "some-base-type",
   555  			Source: atc.Source{
   556  				input.Resource: "source",
   557  			},
   558  		}
   559  	}
   560  
   561  	return versionsDB
   562  }
   563  
   564  func (example Example) assert(
   565  	ctx context.Context,
   566  	setup setupDB,
   567  	alg *algorithm.Algorithm,
   568  	job db.Job,
   569  	inputConfigs db.InputConfigs,
   570  ) {
   571  	ctx, span := tracing.StartSpan(ctx, "assert", tracing.Attrs{})
   572  	defer span.End()
   573  
   574  	span.SetAttributes(label.Int64("seed", ginkgo.GinkgoRandomSeed()))
   575  
   576  	resolved, ok, hasNext, resolvedErr := alg.Compute(ctx, job, inputConfigs)
   577  	if example.Error != nil {
   578  		Expect(resolvedErr).To(Equal(example.Error))
   579  	} else {
   580  		Expect(resolvedErr).ToNot(HaveOccurred())
   581  
   582  		prettyValues := map[string]string{}
   583  		erroredValues := map[string]string{}
   584  		passedJobs := map[string][]int{}
   585  		for name, inputSource := range resolved {
   586  			if inputSource.ResolveError != "" {
   587  				erroredValues[name] = string(inputSource.ResolveError)
   588  			} else {
   589  				if ok {
   590  					var versionID int
   591  					err := setup.psql.Select("v.id").
   592  						From("resource_config_versions v").
   593  						Join("resources r ON r.resource_config_scope_id = v.resource_config_scope_id").
   594  						Where(sq.Eq{
   595  							"v.version_md5": inputSource.Input.AlgorithmVersion.Version,
   596  							"r.id":          inputSource.Input.ResourceID,
   597  						}).
   598  						QueryRow().
   599  						Scan(&versionID)
   600  					Expect(err).ToNot(HaveOccurred())
   601  
   602  					prettyValues[name] = setup.versionIDs.Name(versionID)
   603  
   604  					passedJobs[name] = inputSource.PassedBuildIDs
   605  				}
   606  			}
   607  		}
   608  
   609  		actualResult := Result{OK: ok}
   610  		if len(erroredValues) != 0 {
   611  			actualResult.Errors = erroredValues
   612  		}
   613  
   614  		if example.Result.PassedBuildIDs != nil {
   615  			actualResult.PassedBuildIDs = passedJobs
   616  		}
   617  
   618  		if ok {
   619  			actualResult.Values = prettyValues
   620  		}
   621  
   622  		Expect(actualResult.OK).To(Equal(example.Result.OK))
   623  		Expect(actualResult.Errors).To(Equal(example.Result.Errors))
   624  		Expect(actualResult.Values).To(Equal(example.Result.Values))
   625  
   626  		for input, buildIDs := range example.Result.PassedBuildIDs {
   627  			Expect(actualResult.PassedBuildIDs[input]).To(ConsistOf(buildIDs))
   628  		}
   629  
   630  		if example.Result.ExpectedMigrated != nil {
   631  			rows, err := setup.psql.Select("build_id", "job_id", "outputs", "rerun_of").
   632  				From("successful_build_outputs").
   633  				Query()
   634  			Expect(err).ToNot(HaveOccurred())
   635  
   636  			actualMigrated := map[int]map[int][]string{}
   637  			jobToBuilds := map[int]int{}
   638  			rerunOfBuilds := map[int]int{}
   639  			for rows.Next() {
   640  				var buildID, jobID int
   641  				var rerunOf sql.NullInt64
   642  				var outputs string
   643  
   644  				err = rows.Scan(&buildID, &jobID, &outputs, &rerunOf)
   645  				Expect(err).ToNot(HaveOccurred())
   646  
   647  				_, exists := actualMigrated[buildID]
   648  				Expect(exists).To(BeFalse())
   649  
   650  				buildOutputs := map[int][]string{}
   651  				err = json.Unmarshal([]byte(outputs), &buildOutputs)
   652  				actualMigrated[buildID] = buildOutputs
   653  
   654  				jobToBuilds[buildID] = jobID
   655  
   656  				if rerunOf.Valid {
   657  					rerunOfBuilds[buildID] = int(rerunOf.Int64)
   658  				}
   659  			}
   660  
   661  			Expect(actualMigrated).To(Equal(example.Result.ExpectedMigrated))
   662  
   663  			for buildID, jobID := range jobToBuilds {
   664  				var actualJobID int
   665  
   666  				err = setup.psql.Select("job_id").
   667  					From("builds").
   668  					Where(sq.Eq{
   669  						"id": buildID,
   670  					}).
   671  					QueryRow().
   672  					Scan(&actualJobID)
   673  				Expect(err).ToNot(HaveOccurred())
   674  				Expect(jobID).To(Equal(actualJobID))
   675  			}
   676  
   677  			for buildID, rerunBuildID := range rerunOfBuilds {
   678  				var actualRerunOfBuildID int
   679  
   680  				err = setup.psql.Select("rerun_of").
   681  					From("builds").
   682  					Where(sq.Eq{
   683  						"id": buildID,
   684  					}).
   685  					QueryRow().
   686  					Scan(&actualRerunOfBuildID)
   687  				Expect(err).ToNot(HaveOccurred())
   688  				Expect(rerunBuildID).To(Equal(actualRerunOfBuildID))
   689  			}
   690  		}
   691  
   692  		if example.Result.HasNext == true {
   693  			Expect(hasNext).To(Equal(true))
   694  		}
   695  
   696  		if example.Result.NoNext == true {
   697  			Expect(hasNext).To(Equal(false))
   698  		}
   699  	}
   700  }
   701  
   702  type setupDB struct {
   703  	teamID     int
   704  	pipelineID int
   705  
   706  	jobIDs      StringMapping
   707  	resourceIDs StringMapping
   708  	versionIDs  StringMapping
   709  
   710  	psql sq.StatementBuilderType
   711  }
   712  
   713  func (s setupDB) insertJob(jobName string) int {
   714  	id := s.jobIDs.ID(jobName)
   715  	_, err := s.psql.Insert("jobs").
   716  		Columns("id", "pipeline_id", "name", "config", "active").
   717  		Values(id, s.pipelineID, jobName, "{}", true).
   718  		Suffix("ON CONFLICT DO NOTHING").
   719  		Exec()
   720  	Expect(err).ToNot(HaveOccurred())
   721  
   722  	return id
   723  }
   724  
   725  func (s setupDB) insertResource(name string, scope *int) int {
   726  	resourceID := s.resourceIDs.ID(name)
   727  
   728  	// just make them one-to-one
   729  	resourceConfigID := resourceID
   730  
   731  	j, err := json.Marshal(atc.Source{name: "source"})
   732  	Expect(err).ToNot(HaveOccurred())
   733  
   734  	_, err = s.psql.Insert("resource_configs").
   735  		Columns("id", "source_hash", "base_resource_type_id").
   736  		Values(resourceConfigID, fmt.Sprintf("%x", sha256.Sum256(j)), 1).
   737  		Suffix("ON CONFLICT DO NOTHING").
   738  		Exec()
   739  	Expect(err).ToNot(HaveOccurred())
   740  
   741  	var rcsID sql.NullInt64
   742  	if scope != nil {
   743  		_, err = s.psql.Insert("resource_config_scopes").
   744  			Columns("id", "resource_config_id").
   745  			Values(*scope, resourceConfigID).
   746  			Suffix("ON CONFLICT DO NOTHING").
   747  			Exec()
   748  		Expect(err).ToNot(HaveOccurred())
   749  
   750  		rcsID = sql.NullInt64{
   751  			Int64: int64(*scope),
   752  			Valid: true,
   753  		}
   754  	}
   755  
   756  	_, err = s.psql.Insert("resources").
   757  		Columns("id", "name", "type", "config", "pipeline_id", "resource_config_id", "resource_config_scope_id").
   758  		Values(resourceID, name, fmt.Sprintf("%s-type", name), "{}", s.pipelineID, resourceID, rcsID).
   759  		Suffix("ON CONFLICT (name, pipeline_id) DO UPDATE SET id = EXCLUDED.id, resource_config_id = EXCLUDED.resource_config_id, resource_config_scope_id = EXCLUDED.resource_config_scope_id").
   760  		Exec()
   761  	Expect(err).ToNot(HaveOccurred())
   762  
   763  	return resourceID
   764  }
   765  
   766  func (s setupDB) insertRowVersion(resources map[string]atc.ResourceConfig, row DBRow) {
   767  	resourceID := s.resourceIDs.ID(row.Resource)
   768  	versionID := s.versionIDs.ID(row.Version)
   769  
   770  	var scope *int
   771  	if !row.NoResourceConfigScope {
   772  		scope = &resourceID
   773  	}
   774  
   775  	s.insertResource(row.Resource, scope)
   776  	resources[row.Resource] = atc.ResourceConfig{
   777  		Name: row.Resource,
   778  		Type: "some-base-type",
   779  		Source: atc.Source{
   780  			row.Resource: "source",
   781  		},
   782  	}
   783  
   784  	if row.NoResourceConfigScope || row.DoNotInsertVersion {
   785  		// there's no version to insert if it has no scope
   786  		return
   787  	}
   788  
   789  	versionJSON, err := json.Marshal(atc.Version{"ver": row.Version})
   790  	Expect(err).ToNot(HaveOccurred())
   791  
   792  	_, err = s.psql.Insert("resource_config_versions").
   793  		Columns("id", "resource_config_scope_id", "version", "version_md5", "check_order").
   794  		Values(versionID, resourceID, versionJSON, sq.Expr("md5(?)", versionJSON), row.CheckOrder).
   795  		Suffix("ON CONFLICT DO NOTHING").
   796  		Exec()
   797  	Expect(err).ToNot(HaveOccurred())
   798  
   799  	if row.Disabled {
   800  		_, err = s.psql.Insert("resource_disabled_versions").
   801  			Columns("resource_id", "version_md5").
   802  			Values(resourceID, sq.Expr("md5(?)", versionJSON)).
   803  			Suffix("ON CONFLICT DO NOTHING").
   804  			Exec()
   805  		Expect(err).ToNot(HaveOccurred())
   806  	}
   807  }
   808  
   809  func (s setupDB) insertRowBuild(row DBRow, needsV6Migration bool) {
   810  	jobID := s.insertJob(row.Job)
   811  
   812  	var rerunOf sql.NullInt64
   813  	if row.RerunOfBuildID != 0 {
   814  		rerunOf = sql.NullInt64{Int64: int64(row.RerunOfBuildID), Valid: true}
   815  	}
   816  
   817  	buildStatus := "succeeded"
   818  	if len(row.BuildStatus) != 0 {
   819  		buildStatus = row.BuildStatus
   820  	}
   821  
   822  	var existingJobID int
   823  	err := s.psql.Insert("builds").
   824  		Columns("team_id", "id", "job_id", "name", "status", "scheduled", "inputs_ready", "rerun_of", "needs_v6_migration").
   825  		Values(s.teamID, row.BuildID, jobID, row.BuildID, buildStatus, true, true, rerunOf, needsV6Migration).
   826  		Suffix("ON CONFLICT (id) DO UPDATE SET name = excluded.name").
   827  		Suffix("RETURNING job_id").
   828  		QueryRow().
   829  		Scan(&existingJobID)
   830  	Expect(err).ToNot(HaveOccurred())
   831  
   832  	Expect(existingJobID).To(Equal(jobID), fmt.Sprintf("build ID %d already used by job other than %s", row.BuildID, row.Job))
   833  
   834  	_, err = s.psql.Update("jobs").
   835  		Set("latest_completed_build_id", row.BuildID).
   836  		Where(sq.Eq{
   837  			"id": jobID,
   838  		}).
   839  		Exec()
   840  	Expect(err).ToNot(HaveOccurred())
   841  }
   842  
   843  func (s setupDB) insertBuildPipe(row DBRow) {
   844  	_, err := s.psql.Insert("build_pipes").
   845  		Columns("from_build_id", "to_build_id").
   846  		Values(row.FromBuildID, row.ToBuildID).
   847  		Suffix("ON CONFLICT DO NOTHING").
   848  		Exec()
   849  	Expect(err).ToNot(HaveOccurred())
   850  }
   851  
   852  func (s setupDB) insertPinned(resourceID int, version atc.Version) {
   853  	versionJSON, err := json.Marshal(version)
   854  	Expect(err).ToNot(HaveOccurred())
   855  
   856  	_, err = s.psql.Insert("resource_pins").
   857  		Columns("resource_id", "version", "comment_text").
   858  		Values(resourceID, versionJSON, "").
   859  		Suffix("ON CONFLICT DO NOTHING").
   860  		Exec()
   861  	Expect(err).ToNot(HaveOccurred())
   862  }