experiment/resultstore/convert.go
func convert(project, details string, url gcs.Path, result downloadResult) (resultstore.Invocation, resultstore.Target, resultstore.Test) {
	started := result.started
	finished := result.finished
	artifacts := result.artifactURLs

	basePath := trailingSlash(url.String())
	artifactsPath := basePath + "artifacts/"
	buildLog := basePath + "build-log.txt"
	bucket := url.Bucket()
	inv := resultstore.Invocation{
		Project: project,
		Details: details,
		Files: []resultstore.File{
			{
				ID:          resultstore.InvocationLog,
				ContentType: "text/plain",
				URL:         buildLog, // ensure build-log.txt appears as the invocation log
			},
		},
	}

	// Files need a unique identifier, trim the common prefix and provide this.
	uniqPath := func(s string) string { return strings.TrimPrefix(s, basePath) }

	for i, a := range artifacts {
		artifacts[i] = "gs://" + bucket + "/" + a
	}

	for _, a := range artifacts { // add started.json, etc to the invocation artifact list.
		if strings.HasPrefix(a, artifactsPath) {
			continue // things under artifacts/ are owned by the test
		}
		if a == buildLog {
			continue // Handle this in InvocationLog
		}
		inv.Files = append(inv.Files, resultstore.File{
			ID:          uniqPath(a),
			ContentType: "text/plain",
			URL:         a,
		})
	}
	if started.Timestamp > 0 {
		inv.Start = time.Unix(started.Timestamp, 0)
		if finished.Timestamp != nil {
			inv.Duration = time.Duration(*finished.Timestamp-started.Timestamp) * time.Second
		}
	}

	const day = 24 * 60 * 60
	switch {
	case finished.Timestamp == nil && started.Timestamp < time.Now().Unix()+day:
		inv.Status = resultstore.Running
		inv.Description = "In progress..."
	case finished.Passed != nil && *finished.Passed:
		inv.Status = resultstore.Passed
		inv.Description = "Passed"
	case finished.Timestamp == nil:
		inv.Status = resultstore.Failed
		inv.Description = "Timed out"
	default:
		inv.Status = resultstore.Failed
		inv.Description = "Failed"
	}

	test := resultstore.Test{
		Action: resultstore.Action{
			Node: started.Node,
		},
		Suite: resultstore.Suite{
			Name: "test",
			Files: []resultstore.File{
				{
					ID:          resultstore.TargetLog,
					ContentType: "text/plain",
					URL:         buildLog, // ensure build-log.txt appears as the target log.
				},
			},
		},
	}

	for _, suiteMeta := range result.suiteMetas {
		child := convertSuiteMeta(suiteMeta)
		test.Suite.Suites = append(test.Suite.Suites, child)
		test.Suite.Files = append(test.Suite.Files, child.Files...)
	}
	for _, a := range artifacts {
		if !strings.HasPrefix(a, artifactsPath) {
			continue // Non-artifacts (started.json, etc) are owned by the invocation
		}
		if a == buildLog {
			continue // Already in the list.
		}
		// TODO(fejta): use set.Strings instead
		var found bool
		for _, sm := range result.suiteMetas {
			if sm.Path == a {
				found = true
				break
			}
		}
		if found {
			continue
		}
		test.Suite.Files = append(test.Suite.Files, resultstore.File{
			ID:          uniqPath(a),
			ContentType: "text/plain",
			URL:         a,
		})
	}

	test.Suite.Start = inv.Start
	test.Action.Start = inv.Start
	test.Suite.Duration = inv.Duration
	test.Action.Duration = inv.Duration
	test.Status = inv.Status
	test.Description = inv.Description

	target := resultstore.Target{
		Start:       inv.Start,
		Duration:    inv.Duration,
		Status:      inv.Status,
		Description: inv.Description,
	}

	return inv, target, test
}
func convert(project, details string, url gcs.Path, result downloadResult) (resultstore.Invocation, resultstore.Target, resultstore.Test) {
	started := result.started
	finished := result.finished
	artifacts := result.artifactURLs

	basePath := trailingSlash(url.String())
	artifactsPath := basePath + "artifacts/"
	buildLog := basePath + "build-log.txt"
	bucket := url.Bucket()
	inv := resultstore.Invocation{
		Project: project,
		Details: details,
		Files: []resultstore.File{
			{
				ID:          resultstore.InvocationLog,
				ContentType: "text/plain",
				URL:         buildLog, // ensure build-log.txt appears as the invocation log
			},
		},
	}

	// Files need a unique identifier, trim the common prefix and provide this.
	uniqPath := func(s string) string { return strings.TrimPrefix(s, basePath) }

	for i, a := range artifacts {
		artifacts[i] = "gs://" + bucket + "/" + a
	}

	for _, a := range artifacts { // add started.json, etc to the invocation artifact list.
		if strings.HasPrefix(a, artifactsPath) {
			continue // things under artifacts/ are owned by the test
		}
		if a == buildLog {
			continue // Handle this in InvocationLog
		}
		inv.Files = append(inv.Files, resultstore.File{
			ID:          uniqPath(a),
			ContentType: "text/plain",
			URL:         a,
		})
	}
	if started.Timestamp > 0 {
		inv.Start = time.Unix(started.Timestamp, 0)
		if finished.Timestamp != nil {
			inv.Duration = time.Duration(*finished.Timestamp-started.Timestamp) * time.Second
		}
	}

	const day = 24 * 60 * 60
	switch {
	case finished.Timestamp == nil && started.Timestamp < time.Now().Unix()+day:
		inv.Status = resultstore.Running
		inv.Description = "In progress..."
	case finished.Passed != nil && *finished.Passed:
		inv.Status = resultstore.Passed
		inv.Description = "Passed"
	case finished.Timestamp == nil:
		inv.Status = resultstore.Failed
		inv.Description = "Timed out"
	default:
		inv.Status = resultstore.Failed
		inv.Description = "Failed"
	}

	test := resultstore.Test{
		Action: resultstore.Action{
			Node: started.Node,
		},
		Suite: resultstore.Suite{
			Name: "test",
			Files: []resultstore.File{
				{
					ID:          resultstore.TargetLog,
					ContentType: "text/plain",
					URL:         buildLog, // ensure build-log.txt appears as the target log.
				},
			},
		},
	}

	for _, suiteMeta := range result.suiteMetas {
		child := convertSuiteMeta(suiteMeta)
		test.Suite.Suites = append(test.Suite.Suites, child)
		test.Suite.Files = append(test.Suite.Files, child.Files...)
	}
	for _, a := range artifacts {
		if !strings.HasPrefix(a, artifactsPath) {
			continue // Non-artifacts (started.json, etc) are owned by the invocation
		}
		if a == buildLog {
			continue // Already in the list.
		}
		// TODO(fejta): use set.Strings instead
		var found bool
		for _, sm := range result.suiteMetas {
			if sm.Path == a {
				found = true
				break
			}
		}
		if found {
			continue
		}
		test.Suite.Files = append(test.Suite.Files, resultstore.File{
			ID:          uniqPath(a),
			ContentType: "text/plain",
			URL:         a,
		})
	}

	test.Suite.Start = inv.Start
	test.Action.Start = inv.Start
	test.Suite.Duration = inv.Duration
	test.Action.Duration = inv.Duration
	test.Status = inv.Status
	test.Description = inv.Description

	target := resultstore.Target{
		Start:       inv.Start,
		Duration:    inv.Duration,
		Status:      inv.Status,
		Description: inv.Description,
	}

	return inv, target, test
}
// Convert converts build metadata stored in gcp into the corresponding ResultStore Invocation, Target and Test.