| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| package runtests |
| |
| import ( |
| "archive/tar" |
| "bytes" |
| "context" |
| "encoding/json" |
| "fmt" |
| "io" |
| "net" |
| "os" |
| "path" |
| "path/filepath" |
| "time" |
| |
| "go.fuchsia.dev/tools/botanist" |
| "go.fuchsia.dev/tools/logger" |
| "go.fuchsia.dev/tools/retry" |
| "go.fuchsia.dev/tools/tarutil" |
| "go.fuchsia.dev/tools/tftp" |
| ) |
| |
| // PollForSummary polls a node waiting for a summary.json to be written; this relies on |
| // runtests having been run on target. |
| func PollForSummary(ctx context.Context, addr *net.UDPAddr, summaryFilename, testResultsDir, outputArchive string, filePollInterval time.Duration) error { |
| t := tftp.NewClient() |
| tftpAddr := &net.UDPAddr{ |
| IP: addr.IP, |
| Port: tftp.ClientPort, |
| Zone: addr.Zone, |
| } |
| var buffer bytes.Buffer |
| var writer io.WriterTo |
| var err error |
| err = retry.Retry(ctx, retry.NewConstantBackoff(filePollInterval), func() error { |
| writer, err = t.Receive(tftpAddr, path.Join(testResultsDir, summaryFilename)) |
| return err |
| }, nil) |
| if err != nil { |
| return fmt.Errorf("timed out waiting for tests to complete: %v", err) |
| } |
| |
| logger.Debugf(ctx, "reading %q\n", summaryFilename) |
| |
| if _, err := writer.WriteTo(&buffer); err != nil { |
| return fmt.Errorf("failed to receive summary file: %v", err) |
| } |
| |
| // Parse and save the summary.json file. |
| var result TestSummary |
| if err := json.Unmarshal(buffer.Bytes(), &result); err != nil { |
| return fmt.Errorf("cannot unmarshall test results: %v", err) |
| } |
| |
| outFile, err := os.OpenFile(outputArchive, os.O_WRONLY|os.O_CREATE, 0666) |
| if err != nil { |
| return fmt.Errorf("failed to create file %s: %v", outputArchive, err) |
| } |
| |
| tw := tar.NewWriter(outFile) |
| defer tw.Close() |
| |
| if err = tarutil.TarBuffer(tw, buffer.Bytes(), summaryFilename); err != nil { |
| return err |
| } |
| |
| logger.Debugf(ctx, "copying test output\n") |
| |
| // Tar in a subroutine while busy-printing so that we do not hit an i/o timeout when |
| // dealing with large files. |
| c := make(chan error) |
| go func() { |
| // Copy test output from the node. |
| for _, output := range result.Outputs { |
| remote := filepath.Join(testResultsDir, output) |
| if err = botanist.FetchAndArchiveFile(t, tftpAddr, tw, remote, output); err != nil { |
| c <- err |
| return |
| } |
| } |
| for _, test := range result.Tests { |
| remote := filepath.Join(testResultsDir, test.OutputFile) |
| if err = botanist.FetchAndArchiveFile(t, tftpAddr, tw, remote, test.OutputFile); err != nil { |
| c <- err |
| return |
| } |
| // Copy data sinks if any are present. |
| for _, sinks := range test.DataSinks { |
| for _, sink := range sinks { |
| remote := filepath.Join(testResultsDir, sink.File) |
| if err = botanist.FetchAndArchiveFile(t, tftpAddr, tw, remote, sink.File); err != nil { |
| c <- err |
| return |
| } |
| } |
| } |
| } |
| c <- nil |
| }() |
| |
| logger.Debugf(ctx, "tarring test output...\n") |
| ticker := time.NewTicker(5 * time.Second) |
| for { |
| select { |
| case err := <-c: |
| ticker.Stop() |
| return err |
| case <-ticker.C: |
| logger.Debugf(ctx, "tarring test output...\n") |
| } |
| } |
| } |