[tap] Add support for producing YAML blocks

Change-Id: I9907639e20d6dee66ee9b3b1f0a665a9509848f2
diff --git a/tap/producer.go b/tap/producer.go
index 53d6194..f203a0c 100644
--- a/tap/producer.go
+++ b/tap/producer.go
@@ -62,6 +62,16 @@
 	p.directive = None
 }
 
+// YAML produces a YAML block from the given input. This will indent the input data. The
+// caller should not do this themselves.
+func (p *Producer) YAML(input []byte) {
+	// Chomp empty lines from the end of the document.
+	content := strings.TrimSuffix(string(input), "\n")
+	p.writeln(p.indent("---"))
+	p.writeln(p.indent(content))
+	p.writeln(p.indent("..."))
+}
+
 // Todo returns a new Producer that prints TODO directives.
 func (p *Producer) Todo() *Producer {
 	p.directive = Todo
@@ -75,8 +85,7 @@
 }
 
 func (p *Producer) writeln(format string, args ...interface{}) {
-	line := strings.TrimSpace(fmt.Sprintf(format, args...)) + "\n"
-	fmt.Fprintf(p.writer(), line)
+	fmt.Fprintln(p.writer(), fmt.Sprintf(format, args...))
 }
 
 // writer initializes the Writer to use for this Producer, in case the Producer was
@@ -87,3 +96,22 @@
 	}
 	return p.output
 }
+
+// Indent indents every line of the input text with a single space.
+func (p *Producer) indent(input string) string {
+	return string(p.indentBytes([]byte(input)))
+}
+
+// IndentBytes indents every line of the input text with a single space.
+func (p *Producer) indentBytes(input []byte) []byte {
+	var output []byte
+	startOfLine := true
+	for _, c := range input {
+		if startOfLine && c != '\n' {
+			output = append(output, ' ')
+		}
+		output = append(output, c)
+		startOfLine = c == '\n'
+	}
+	return output
+}
diff --git a/tap/producer_test.go b/tap/producer_test.go
index 8e56ebb..e5a5632 100644
--- a/tap/producer_test.go
+++ b/tap/producer_test.go
@@ -6,8 +6,10 @@
 
 import (
 	"os"
+	"time"
 
 	"fuchsia.googlesource.com/tools/tap"
+	"gopkg.in/yaml.v2"
 )
 
 func ExampleProducer_single_test() {
@@ -45,15 +47,11 @@
 	p.Plan(4)
 	p.Ok(true, "- this test passed")
 	p.Ok(false, "")
-	p.Ok(false, "- this test failed")
-	p.Skip().Ok(true, "this test is skippable")
 	// Output:
 	// TAP version 13
 	// 1..4
 	// ok 1 - this test passed
 	// not ok 2
-	// not ok 3 - this test failed
-	// ok 4 # SKIP this test is skippable
 }
 
 func ExampleProducer_skip_todo_alternating() {
@@ -71,3 +69,31 @@
 	// not ok 3 # SKIP skipped another
 	// ok 4 # TODO please don't write code like this
 }
+
+func ExampleProducer_YAML() {
+	p := tap.NewProducer(os.Stdout)
+	p.Plan(1)
+	p.Ok(true, "passed")
+	bytes, err := yaml.Marshal(struct {
+		Name  string    `yaml:"name"`
+		Start time.Time `yaml:"start_time"`
+		End   time.Time `yaml:"end_time"`
+	}{
+		Name:  "foo_test",
+		Start: time.Date(2019, 1, 1, 12, 30, 0, 0, time.UTC),
+		End:   time.Date(2019, 1, 1, 12, 40, 0, 0, time.UTC),
+	})
+	if err != nil {
+		panic(err)
+	}
+	p.YAML(bytes)
+	// Output:
+	// TAP version 13
+	// 1..1
+	// ok 1 passed
+	//  ---
+	//  name: foo_test
+	//  start_time: 2019-01-01T12:30:00Z
+	//  end_time: 2019-01-01T12:40:00Z
+	//  ...
+}