blob: d53e36d5f80115988a0b6ca958dd9dd67cc288ab [file] [log] [blame]
// Copyright 2020 The Chromium 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 cmd
import (
"bufio"
"context"
"fmt"
"github.com/maruel/subcommands"
"go.chromium.org/luci/common/cli"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"infra/cmdsupport/cmdlib"
"infra/cros/cmd/tclint/internal/metadata"
metadataPB "go.chromium.org/chromiumos/config/go/api/test/metadata/v1"
)
// Metadata subcommand: Lint test metadata.
var Metadata = &subcommands.Command{
UsageLine: "metadata [FLAGS...] INPUT_FILE_GLOB [INPUT_FILE_GLOB...]",
ShortDesc: "Lint Chrome OS test metadata specification.",
LongDesc: `Lint a (complete) specification of a Chrome OS test metadata.
The test metadata must be specified as a metadata.Specification payload as
defined at
https://chromium.googlesource.com/chromiumos/config/+/refs/heads/master/proto/chromiumos/config/api/test/metadata/v1/metadata.proto
The lint includes some global uniqueness checks. Thus, validation may be
incomplete if a partial test metadata specification is provided as input.
The test metadata specification may be split over multiple files and
directories, provided via glob patterns in the positional arguments.`,
CommandRun: func() subcommands.CommandRun {
c := &metadataRun{}
c.Flags.BoolVar(
&c.binaryFormat,
"binary",
false,
`Decode input protobuf payload from the binary wire format.
By default, input is assumed to be encoded as JSON.`,
)
return c
},
}
type metadataRun struct {
subcommands.CommandRunBase
binaryFormat bool
}
// Run implements the subcommands.CommandRun interface.
func (c *metadataRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
ctx := cli.GetContext(a, c, env)
ctx = logging.SetLevel(ctx, logging.Info)
if err := c.innerRun(ctx, a, args); err != nil {
cmdlib.PrintError(a, err)
return 1
}
return 0
}
type metadataFile struct {
path string
payload *metadataPB.Specification
errs errors.MultiError
}
func (c *metadataRun) innerRun(ctx context.Context, a subcommands.Application, args []string) error {
if len(args) == 0 {
return errors.Reason("no input files").Err()
}
ms, err := c.load(args)
if err != nil {
return err
}
w := bufio.NewWriter(a.GetOut())
defer w.Flush()
for _, m := range ms {
fmt.Fprintf(w, "### Linting file %s...\n", m.path)
if m.errs != nil {
fmt.Fprintf(w, "Failed to load file: %s\n", m.errs)
continue
}
r := metadata.Lint(m.payload)
for _, l := range r.Display() {
fmt.Fprintln(w, l)
}
if r.Errors == nil {
fmt.Fprintf(w, "All clean!\n")
}
fmt.Fprintf(w, "\n")
}
return nil
}
func (c *metadataRun) load(globs []string) ([]*metadataFile, error) {
resp := make([]*metadataFile, 0, len(globs))
err := forEachFile(
globs,
func(f string) {
pf := &metadataFile{
path: f,
}
if p, err := c.loadOne(f); err != nil {
pf.errs = errors.NewMultiError(err)
} else {
pf.payload = p
}
resp = append(resp, pf)
},
)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *metadataRun) loadOne(path string) (*metadataPB.Specification, error) {
var p metadataPB.Specification
var err error
if c.binaryFormat {
err = loadFromBinary(path, &p)
} else {
err = loadFromJSON(path, &p)
}
return &p, err
}