[engine] Error out if no shac.star file found

Previously shac would hang forever on the `select` on line 276 of run.go
when no shac.star files were found in recursive mode, which can
confusingly happen if shac.star files do exist but aren't staged in Git
yet.

Change-Id: Ifdd8692c58c5378c9df2c0e4f0c0079176463afb
Reviewed-on: https://fuchsia-review.googlesource.com/c/shac-project/shac/+/861256
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
Commit-Queue: Oliver Newman <olivernewman@google.com>
Reviewed-by: Marc-Antoine Ruel <maruel@google.com>
diff --git a/internal/engine/run.go b/internal/engine/run.go
index 6f401bd..0764848 100644
--- a/internal/engine/run.go
+++ b/internal/engine/run.go
@@ -209,6 +209,9 @@
 		// parallelism.
 		// Discover all the main files via the SCM. This enables us to not walk
 		// ignored files.
+		// TODO(olivernewman): Because we use scm to discover shac.star files,
+		// shac will ignore any unstaged shac.star files. This is
+		// counterintuitive, we should respect unstaged shac.star files.
 		files, err := scm.allFiles(ctx, false)
 		if err != nil {
 			return err
@@ -236,6 +239,9 @@
 					})
 			}
 		}
+		if len(shacStates) == 0 {
+			return fmt.Errorf("no %s files found", main)
+		}
 	} else {
 		shacStates = []*shacState{
 			{
diff --git a/internal/engine/run_test.go b/internal/engine/run_test.go
index 8b3fc21..3104ece 100644
--- a/internal/engine/run_test.go
+++ b/internal/engine/run_test.go
@@ -27,7 +27,6 @@
 	"path/filepath"
 	"runtime"
 	"sort"
-	"strconv"
 	"strings"
 	"sync"
 	"testing"
@@ -40,16 +39,12 @@
 func TestRun_Fail(t *testing.T) {
 	t.Parallel()
 	data := []struct {
-		o   Options
-		err string
+		name string
+		o    Options
+		err  string
 	}{
 		{
-			Options{
-				config: "/dev/null",
-			},
-			"file not found",
-		},
-		{
+			"config path is a directory",
 			Options{
 				config: ".",
 			},
@@ -61,6 +56,7 @@
 			}(),
 		},
 		{
+			"absolute main path",
 			Options{
 				main: func() string {
 					if runtime.GOOS == "windows" {
@@ -72,16 +68,34 @@
 			"main file must not be an absolute path",
 		},
 		{
+			"malformed config file",
 			Options{
 				config: "testdata/config/syntax.textproto",
 			},
 			// The encoding is not deterministic.
 			"...: unknown field: bad",
 		},
+		{
+			"no shac.star file",
+			Options{
+				Root: t.TempDir(),
+			},
+			// TODO(olivernewman): This error should be more specific, like "no
+			// shac.star file found".
+			"file not found",
+		},
+		{
+			"no shac.star files (recursive)",
+			Options{
+				Root:    t.TempDir(),
+				Recurse: true,
+			},
+			"no shac.star files found",
+		},
 	}
 	for i := range data {
 		i := i
-		t.Run(strconv.Itoa(i), func(t *testing.T) {
+		t.Run(data[i].name, func(t *testing.T) {
 			t.Parallel()
 			o := data[i].o
 			o.Report = &reportNoPrint{t: t}