Repository: goccy/go-yaml
Pull request #542 (2024-11-17 13:53:28)

Fix parser by using yaml-test-suite
Fix some parser's bug by using yaml-test-suite

codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/goccy/go-yaml/pull/542?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
Attention: Patch coverage is `83.83234%` with `27 lines` in your changes missing coverage. Please review.
> Project coverage is 79.71%. Comparing base [(`c312882`)](https://app.codecov.io/gh/goccy/go-yaml/commit/c3128820b19a41b555555e1051b025152c44ba7d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`8c8ef18`)](https://app.codecov.io/gh/goccy/go-yaml/commit/8c8ef18ba3b2e9a2d4ff7ecb5abeb86bd3b3615f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima).

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #542      +/-   ##
==========================================
- Coverage   80.02%   79.71%   -0.31%     
==========================================
  Files          18       18              
  Lines        6262     6374     +112     
==========================================
+ Hits         5011     5081      +70     
- Misses        950      986      +36     
- Partials      301      307       +6     
```

</details>

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index e5341df..3a16169 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1287,6 +1287,7 @@ type MappingValueNode struct {
 	Start       *token.Token
 	Key         MapKeyNode
 	Value       Node
+	Anchor      *AnchorNode
 	FootComment *CommentGroupNode
 }
 
@@ -1597,6 +1598,10 @@ type AnchorNode struct {
 	Value Node
 }
 
+func (n *AnchorNode) stringWithoutComment() string {
+	return n.Value.String()
+}
+
 func (n *AnchorNode) SetName(name string) error {
 	if n.Name == nil {
 		return ErrInvalidAnchorName
@@ -1622,6 +1627,10 @@ func (n *AnchorNode) GetToken() *token.Token {
 	return n.Start
 }
 
+func (n *AnchorNode) GetValue() any {
+	return n.Value.GetToken().Value
+}
+
 // AddColumn add column number to child nodes recursively
 func (n *AnchorNode) AddColumn(col int) {
 	n.Start.AddColumn(col)
@@ -1658,6 +1667,10 @@ type AliasNode struct {
 	Value Node
 }
 
+func (n *AliasNode) stringWithoutComment() string {
+	return n.Value.String()
+}
+
 func (n *AliasNode) SetName(name string) error {
 	if n.Value == nil {
 		return ErrInvalidAliasName
@@ -1683,6 +1696,10 @@ func (n *AliasNode) GetToken() *token.Token {
 	return n.Start
 }
 
+func (n *AliasNode) GetValue() any {
+	return n.Value.GetToken().Value
+}
+
 // AddColumn add column number to child nodes recursively
 func (n *AliasNode) AddColumn(col int) {
 	n.Start.AddColumn(col)
@@ -1745,6 +1762,10 @@ type TagNode struct {
 	Value Node
 }
 
+func (n *TagNode) stringWithoutComment() string {
+	return n.Value.String()
+}
+
 // Read implements (io.Reader).Read
 func (n *TagNode) Read(p []byte) (int, error) {
 	return readNode(p, n)
diff --git a/parser/context.go b/parser/context.go
index cc7d302..2ed9aad 100644
--- a/parser/context.go
+++ b/parser/context.go
@@ -7,8 +7,9 @@ import (
 
 // context context at parsing
 type context struct {
-	path   string
-	isFlow bool
+	path     string
+	isFlow   bool
+	isMapKey bool
 }
 
 var pathSpecialChars = []string{
@@ -49,6 +50,12 @@ func (c *context) withFlow(isFlow bool) *context {
 	return &ctx
 }
 
+func (c *context) withMapKey() *context {
+	ctx := *c
+	ctx.isMapKey = true
+	return &ctx
+}
+
 func newContext() *context {
 	return &context{
 		path: "$",
diff --git a/parser/parser.go b/parser/parser.go
index 169d5ed..715a381 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -254,7 +254,7 @@ func (p *parser) parseTag(ctx *context) (*ast.TagNode, error) {
 		if typ == token.LiteralType || typ == token.FoldedType {
 			value, err = p.parseLiteral(ctx)
 		} else {
-			value = p.parseScalarValue(p.currentToken())
+			value, err = p.parseScalarValueWithComment(ctx, p.currentToken())
 		}
 	case token.SequenceTag,
 		token.SetTag:
@@ -429,7 +429,7 @@ func (p *parser) mapKeyText(n ast.Node) string {
 }
 
 func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
-	key, err := p.parseMapKey(ctx)
+	key, err := p.parseMapKey(ctx.withMapKey())
 	if err != nil {
 		return nil, err
 	}
@@ -497,30 +497,55 @@ func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
 		if antk == nil {
 			return nil, errors.ErrSyntax("required ':' and map value", ntk)
 		}
-		if antk.Type != token.MappingValueType {
-			return nil, errors.ErrSyntax("required ':' and map value", antk)
-		}
 		p.progressIgnoreComment(1)
-		value, err := p.parseToken(ctx, p.currentToken())
+		var comment *ast.CommentGroupNode
+		if tk := p.currentToken(); tk.Type == token.CommentType {
+			comment = p.parseCommentOnly(ctx)
+		}
+		value, err := p.parseMappingValue(ctx)
 		if err != nil {
 			return nil, err
 		}
-		switch value.Type() {
-		case ast.MappingType:
-			c, _ := value.(*ast.MappingNode)
-			comment := c.GetComment()
-			for idx, v := range c.Values {
+		if comment != nil {
+			comment.SetPath(value.GetPath())
+			if err := value.SetComment(comment); err != nil {
+				return nil, err
+			}
+		}
+		switch v := value.(type) {
+		case *ast.MappingNode:
+			comment := v.GetComment()
+			for idx, val := range v.Values {
 				if idx == 0 && comment != nil {
-					if err := v.SetComment(comment); err != nil {
+					if err := val.SetComment(comment); err != nil {
 						return nil, err
 					}
 				}
-				node.Values = append(node.Values, v)
+				node.Values = append(node.Values, val)
+			}
+		case *ast.MappingValueNode:
+			node.Values = append(node.Values, v)
+		case *ast.AnchorNode:
+			switch anchorV := v.Value.(type) {
+			case *ast.MappingNode:
+				comment := anchorV.GetComment()
+				for idx, val := range anchorV.Values {
+					if idx == 0 && comment != nil {
+						if err := val.SetComment(comment); err != nil {
+							return nil, err
+						}
+					}
+					val.Anchor = v
+					node.Values = append(node.Values, val)
+				}
+			case *ast.MappingValueNode:
+				anchorV.Anchor = v
+				node.Values = append(node.Values, anchorV)
+			default:
+				return nil, fmt.Errorf("failed to parse mapping value node. anchor node is %s", anchorV.Type())
 			}
-		case ast.MappingValueType:
-			node.Values = append(node.Values, value.(*ast.MappingValueNode))
 		default:
-			return nil, fmt.Errorf("failed to parse mapping value node node is %s", value.Type())
+			return nil, fmt.Errorf("failed to parse mapping value node. node is %s", value.Type())
 		}
 		ntk = p.nextNotCommentToken()
 		antk = p.afterNextNotCommentToken()
@@ -657,11 +682,15 @@ func (p *parser) parseAnchor(ctx *context) (*ast.AnchorNode, error) {
 		return nil, errors.ErrSyntax("unexpected anchor. anchor name is undefined", tk)
 	}
 	p.progress(1) // skip anchor token
-	name, err := p.parseToken(ctx, p.currentToken())
+	anchorNameTk := p.currentToken()
+	anchorNameNode, err := p.parseScalarValueWithComment(ctx, anchorNameTk)
 	if err != nil {
 		return nil, err
 	}
-	anchor.Name = name
+	if anchorNameNode == nil {
+		return nil, errors.ErrSyntax("unexpected anchor. anchor name is not scalar value", anchorNameTk)
+	}
+	anchor.Name = anchorNameNode
 	ntk = p.nextToken()
 	if ntk == nil {
 		return nil, errors.ErrSyntax("unexpected anchor. anchor value is undefined", p.currentToken())
@@ -684,17 +713,21 @@ func (p *parser) parseAlias(ctx *context) (*ast.AliasNode, error) {
 		return nil, errors.ErrSyntax("unexpected alias. alias name is undefined", tk)
 	}
 	p.progress(1) // skip alias token
-	name, err := p.parseToken(ctx, p.currentToken())
+	aliasNameTk := p.currentToken()
+	aliasNameNode, err := p.parseScalarValueWithComment(ctx, aliasNameTk)
 	if err != nil {
 		return nil, err
 	}
-	alias.Value = name
+	if aliasNameNode == nil {
+		return nil, errors.ErrSyntax("unexpected alias. alias name is not scalar value", aliasNameTk)
+	}
+	alias.Value = aliasNameNode
 	return alias, nil
 }
 
 func (p *parser) parseMapKey(ctx *context) (ast.MapKeyNode, error) {
 	tk := p.currentToken()
-	if value := p.parseScalarValue(tk); value != nil {
+	if value, _ := p.parseScalarValueWithComment(ctx, tk); value != nil {
 		return value, nil
 	}
 	switch tk.Type {
@@ -702,22 +735,17 @@ func (p *parser) parseMapKey(ctx *context) (ast.MapKeyNode, error) {
 		return ast.MergeKey(tk), nil
 	case token.MappingKeyType:
 		return p.parseMappingKey(ctx)
+	case token.TagType:
+		return p.parseTag(ctx)
 	}
 	return nil, errors.ErrSyntax("unexpected mapping key", tk)
 }
 
-func (p *parser) parseStringValue(tk *token.Token) *ast.StringNode {
-	switch tk.Type {
-	case token.StringType,
-		token.SingleQuoteType,
-		token.DoubleQuoteType:
-		return ast.String(tk)
-	}
-	return nil
-}
-
 func (p *parser) parseScalarValueWithComment(ctx *context, tk *token.Token) (ast.ScalarNode, error) {
-	node := p.parseScalarValue(tk)
+	node, err := p.parseScalarValue(ctx, tk)
+	if err != nil {
+		return nil, err
+	}
 	if node == nil {
 		return nil, nil
 	}
@@ -731,28 +759,32 @@ func (p *parser) parseScalarValueWithComment(ctx *context, tk *token.Token) (ast
 	return node, nil
 }
 
-func (p *parser) parseScalarValue(tk *token.Token) ast.ScalarNode {
-	if node := p.parseStringValue(tk); node != nil {
-		return node
-	}
+func (p *parser) parseScalarValue(ctx *context, tk *token.Token) (ast.ScalarNode, error) {
 	switch tk.Type {
 	case token.NullType:
-		return ast.Null(tk)
+		return ast.Null(tk), nil
 	case token.BoolType:
-		return ast.Bool(tk)
+		return ast.Bool(tk), nil
 	case token.IntegerType,
 		token.BinaryIntegerType,
 		token.OctetIntegerType,
 		token.HexIntegerType:
-		return ast.Integer(tk)
+		return ast.Integer(tk), nil
 	case token.FloatType:
-		return ast.Float(tk)
+		return ast.Float(tk), nil
 	case token.InfinityType:
-		return ast.Infinity(tk)
+		return ast.Infinity(tk), nil
 	case token.NanType:
-		return ast.Nan(tk)
+		return ast.Nan(tk), nil
+	case token.StringType, token.SingleQuoteType,
+		token.DoubleQuoteType:
+		return ast.String(tk), nil
+	case token.AnchorType:
+		return p.parseAnchor(ctx)
+	case token.AliasType:
+		return p.parseAlias(ctx)
 	}
-	return nil
+	return nil, nil
 }
 
 func (p *parser) parseDirective(ctx *context) (*ast.DirectiveNode, error) {
@@ -925,9 +957,14 @@ func (p *parser) createNodeFromToken(ctx *context, tk *token.Token) (ast.Node, e
 	if tk == nil {
 		return nil, nil
 	}
-	if tk.NextType() == token.MappingValueType {
-		node, err := p.parseMappingValue(ctx)
-		return node, err
+	if !ctx.isMapKey && tk.NextType() == token.MappingValueType {
+		return p.parseMappingValue(ctx)
+	}
+	if tk.Type == token.AliasType {
+		aliasValueTk := p.nextToken()
+		if aliasValueTk != nil && aliasValueTk.NextType() == token.MappingValueType {
+			return p.parseMappingValue(ctx)
+		}
 	}
 	node, err := p.parseScalarValueWithComment(ctx, tk)
 	if err != nil {
@@ -982,13 +1019,34 @@ func (p *parser) parse(ctx *context) (*ast.File, error) {
 		}
 		if doc, ok := node.(*ast.DocumentNode); ok {
 			file.Docs = append(file.Docs, doc)
+		} else if len(file.Docs) == 0 {
+			file.Docs = append(file.Docs, ast.Document(nil, node))
 		} else {
+			lastNode := p.comparableColumnNode(file.Docs[len(file.Docs)-1])
+			curNode := p.comparableColumnNode(node)
+			if lastNode.GetToken().Position.Column != curNode.GetToken().Position.Column {
+				return nil, errors.ErrSyntax("value is not allowed in this context", curNode.GetToken())
+			}
 			file.Docs = append(file.Docs, ast.Document(nil, node))
 		}
 	}
 	return file, nil
 }
 
+func (p *parser) comparableColumnNode(n ast.Node) ast.Node {
+	switch nn := n.(type) {
+	case *ast.MappingNode:
+		if len(nn.Values) != 0 {
+			return nn.Values[0].Key
+		}
+	case *ast.MappingValueNode:
+		return nn.Key
+	case *ast.DocumentNode:
+		return p.comparableColumnNode(nn.Body)
+	}
+	return n
+}
+
 type Mode uint
 
 const (
diff --git a/scanner/context.go b/scanner/context.go
index 015a66c..6898d08 100644
--- a/scanner/context.go
+++ b/scanner/context.go
@@ -151,6 +151,7 @@ func (c *Context) addDocumentIndent(column int) {
 		}
 		// Since addBuf ignore space character, add to the buffer directly.
 		c.buf = append(c.buf, ' ')
+		c.notSpaceCharPos = len(c.buf)
 	}
 }
 
@@ -277,7 +278,7 @@ func (c *Context) bufferedSrc() []rune {
 		if c.hasTrimAllEndNewlineOpt() {
 			// If the '-' flag is specified, all trailing newline characters will be removed.
 			src = []rune(strings.TrimRight(string(src), "\n"))
-		} else {
+		} else if !c.hasKeepAllEndNewlineOpt() {
 			// Normally, all but one of the trailing newline characters are removed.
 			var newLineCharCount int
 			for i := len(src) - 1; i >= 0; i-- {
@@ -295,13 +296,18 @@ func (c *Context) bufferedSrc() []rune {
 		}
 
 		// If the text ends with a space character, remove all of them.
-		src = []rune(strings.TrimRight(string(src), " "))
+		if c.hasTrimAllEndNewlineOpt() {
+			src = []rune(strings.TrimRight(string(src), " "))
+		}
 		if string(src) == "\n" {
 			// If the content consists only of a newline,
 			// it can be considered as the document ending without any specified value,
 			// so it is treated as an empty string.
 			src = []rune{}
 		}
+		if c.hasKeepAllEndNewlineOpt() && len(src) == 0 {
+			src = []rune{'\n'}
+		}
 	}
 	return src
 }
@@ -310,12 +316,17 @@ func (c *Context) hasTrimAllEndNewlineOpt() bool {
 	return strings.HasPrefix(c.docOpt, "-") || strings.HasSuffix(c.docOpt, "-") || c.isRawFolded
 }
 
+func (c *Context) hasKeepAllEndNewlineOpt() bool {
+	return strings.HasPrefix(c.docOpt, "+") || strings.HasSuffix(c.docOpt, "+")
+}
+
 func (c *Context) bufferedToken(pos *token.Position) *token.Token {
 	if c.idx == 0 {
 		return nil
 	}
 	source := c.bufferedSrc()
 	if len(source) == 0 {
+		c.buf = c.buf[:0] // clear value's buffer only.
 		return nil
 	}
 	var tk *token.Token
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 366d18e..a0e74bf 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -232,7 +232,22 @@ func (s *Scanner) scanSingleQuote(ctx *Context) (*token.Token, error) {
 		c := src[idx]
 		ctx.addOriginBuf(c)
 		if s.isNewLineChar(c) {
-			value = append(value, ' ')
+			notSpaceIdx := -1
+			for i := len(value) - 1; i >= 0; i-- {
+				if value[i] == ' ' {
+					continue
+				}
+				notSpaceIdx = i
+				break
+			}
+			if len(value) > notSpaceIdx {
+				value = value[:notSpaceIdx+1]
+			}
+			if isFirstLineChar {
+				value = append(value, '\n')
+			} else {
+				value = append(value, ' ')
+			}
 			isFirstLineChar = true
 			isNewLine = true
 			s.progressLine(ctx)
@@ -301,12 +316,19 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (*token.Token, error) {
 		c := src[idx]
 		ctx.addOriginBuf(c)
 		if s.isNewLineChar(c) {
-			if isFirstLineChar {
-				if value[len(value)-1] == ' ' {
-					value[len(value)-1] = '\n'
-				} else {
-					value = append(value, '\n')
+			notSpaceIdx := -1
+			for i := len(value) - 1; i >= 0; i-- {
+				if value[i] == ' ' {
+					continue
 				}
+				notSpaceIdx = i
+				break
+			}
+			if len(value) > notSpaceIdx {
+				value = value[:notSpaceIdx+1]
+			}
+			if isFirstLineChar {
+				value = append(value, '\n')
 			} else {
 				value = append(value, ' ')
 			}
@@ -415,6 +437,10 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (*token.Token, error) {
 				s.progressLine(ctx)
 				idx++
 				continue
+			case '\t':
+				progress = 1
+				ctx.addOriginBuf(nextChar)
+				value = append(value, nextChar)
 			case ' ':
 				// skip escape character.
 			default:
@@ -423,6 +449,29 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (*token.Token, error) {
 			idx += progress
 			s.progressColumn(ctx, progress)
 			continue
+		} else if c == '\t' {
+			var (
+				foundNotSpaceChar bool
+				progress          int
+			)
+			for i := idx + 1; i < size; i++ {
+				if src[i] == ' ' || src[i] == '\t' {
+					progress++
+					continue
+				}
+				if src[i] == '\n' {
+					break
+				}
+				foundNotSpaceChar = true
+			}
+			if foundNotSpaceChar {
+				value = append(value, c)
+				s.progressColumn(ctx, 1)
+			} else {
+				idx += progress
+				s.progressColumn(ctx, progress)
+			}
+			continue
 		} else if c != '"' {
 			value = append(value, c)
 			isFirstLineChar = false
@@ -537,7 +586,7 @@ func (s *Scanner) scanTag(ctx *Context) bool {
 }
 
 func (s *Scanner) scanComment(ctx *Context) bool {
-	if ctx.existsBuffer() && ctx.previousChar() != ' ' {
+	if ctx.existsBuffer() && (ctx.previousChar() != ' ' && ctx.previousChar() != '\t') {
 		return false
 	}
 
@@ -590,16 +639,20 @@ func (s *Scanner) trimCommentFromDocumentOpt(text string, header rune) (string,
 func (s *Scanner) scanDocument(ctx *Context, c rune) error {
 	ctx.addOriginBuf(c)
 	if ctx.isEOS() {
+		if s.isFirstCharAtLine && c == ' ' {
+			ctx.addDocumentIndent(s.column)
+		} else {
+			ctx.addBuf(c)
+		}
 		ctx.updateDocumentLineIndentColumn(s.column)
 		if err := ctx.validateDocumentLineIndentColumn(); err != nil {
 			invalidTk := token.Invalid(err.Error(), string(ctx.obuf), s.pos())
 			s.progressColumn(ctx, 1)
 			return ErrInvalidToken(invalidTk)
 		}
-		ctx.addBuf(c)
 		value := ctx.bufferedSrc()
 		ctx.addToken(token.String(string(value), string(ctx.obuf), s.pos()))
-		ctx.resetBuffer()
+		ctx.clear()
 		s.progressColumn(ctx, 1)
 	} else if s.isNewLineChar(c) {
 		ctx.addBuf(c)


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 66ad5e3..7552e00 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -1110,12 +1110,11 @@ b
 - c
 `,
 			`
-[4:1] required ':' and map value
+[3:1] unexpected key name
    2 | a: 1
-   3 | b
->  4 | - c
+>  3 | b
        ^
-`,
+   4 | - c`,
 		},
 		{
 			`a: [`,
diff --git a/testdata/yaml-test-suite/yaml.go b/testdata/yaml-test-suite/yaml.go
index 73ac92f..240a648 100644
--- a/testdata/yaml-test-suite/yaml.go
+++ b/testdata/yaml-test-suite/yaml.go
@@ -8,6 +8,7 @@ import (
 	"io/fs"
 	"os"
 	"path/filepath"
+	"runtime"
 	"sort"
 	"strings"
 )
@@ -20,9 +21,15 @@ type TestSuite struct {
 	Error   bool
 }
 
+func curDir() string {
+	_, file, _, _ := runtime.Caller(0) //nolint:dogsled
+	return filepath.Dir(file)
+}
+
 func TestSuites() ([]*TestSuite, error) {
+	dir := curDir()
 	testMap := make(map[string]*TestSuite)
-	if err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
+	if err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
 		if strings.HasSuffix(path, ".go") {
 			// this file.
 			return nil
@@ -33,7 +40,7 @@ func TestSuites() ([]*TestSuite, error) {
 		if err != nil {
 			return err
 		}
-		name := strings.TrimPrefix(path, "yaml-test-suite/")
+		name := strings.TrimPrefix(path, dir+"/")
 		name = strings.TrimSuffix(name, "/"+filepath.Base(name))
 		if _, exists := testMap[name]; !exists {
 			testMap[name] = &TestSuite{}
diff --git a/testdata/yaml_test.go b/testdata/yaml_test.go
index da4461b..d3ca816 100644
--- a/testdata/yaml_test.go
+++ b/testdata/yaml_test.go
@@ -1093,7 +1093,7 @@ func TestRegisterCustomUnmarshaler(t *testing.T) {
 		return nil
 	})
 	var v T
-	if err := yaml.Unmarshal([]byte(`"foo: "bar"`), &v); err != nil {
+	if err := yaml.Unmarshal([]byte(`"foo": "bar"`), &v); err != nil {
 		t.Fatal(err)
 	}
 	if !bytes.Equal(v.Foo, []byte("override")) {
diff --git a/yaml_test_suite_test.go b/yaml_test_suite_test.go
index b627fb4..570984d 100644
--- a/yaml_test_suite_test.go
+++ b/yaml_test_suite_test.go
@@ -13,18 +13,187 @@ import (
 	"github.com/goccy/go-yaml/testdata/yaml-test-suite"
 )
 
-const (
-	wip                   = true
-	successCountThreshold = 199
-)
+var failureTestNames = []string{
+	"anchors-on-empty-scalars",
+	"aliases-in-flow-objects",
+	"aliases-in-explicit-block-mapping",
+	"aliases-in-implicit-block-mapping",
+	"allowed-characters-in-alias",
+	"anchor-before-sequence-entry-on-same-line",
+	"anchor-for-empty-node",
+	"anchor-plus-alias",
+	"anchors-in-mapping",
+	"anchors-with-colon-in-name",
+	"bare-document-after-document-end-marker",
+	"block-mapping-with-missing-keys",
+	"block-mapping-with-missing-values",
+	"block-mapping-with-multiline-scalars",
+	"block-scalar-with-more-spaces-than-first-content-line",
+	"block-scalar-with-wrong-indented-line-after-spaces-only",
+	"colon-at-the-beginning-of-adjacent-flow-scalar",
+	"comment-between-plain-scalar-lines",
+	"comment-in-flow-sequence-before-comma",
+	"comment-without-whitespace-after-doublequoted-scalar",
+	"construct-binary",
+	"dash-in-flow-sequence",
+	"directive-variants/00",
+	"directive-variants/01",
+	"double-quoted-scalar-with-escaped-single-quote",
+	"duplicate-yaml-directive",
+	"escaped-slash-in-double-quotes",
+	"explicit-key-and-value-seperated-by-comment", //nolint: misspell
+	"extra-words-on-yaml-directive",
+	"empty-implicit-key-in-single-pair-flow-sequences",
+	"empty-keys-in-block-and-flow-mapping",
+	"empty-lines-at-end-of-document",
+	"flow-mapping-separate-values",
+	"flow-sequence-in-flow-mapping",
+	"flow-collections-over-many-lines/01",
+	"flow-mapping-colon-on-line-after-key/02",
+	"flow-mapping-edge-cases",
+	"flow-sequence-with-invalid-comma-at-the-beginning",
+	"flow-sequence-with-invalid-extra-comma",
+	"folded-block-scalar",
+	"folded-block-scalar-1-3",
+	"implicit-flow-mapping-key-on-one-line",
+	"invalid-anchor-in-zero-indented-sequence",
+	"invalid-comment-after-comma",
+	"invalid-comment-after-end-of-flow-sequence",
+	"invalid-document-end-marker-in-single-quoted-string",
+	"invalid-document-markers-in-flow-style",
+	"invalid-document-start-marker-in-doublequoted-tring",
+	"invalid-escape-in-double-quoted-string",
+	"invalid-item-after-end-of-flow-sequence",
+	"invalid-mapping-after-sequence",
+	"invalid-mapping-in-plain-single-line-value",
+	"invalid-nested-mapping",
+	"invalid-scalar-after-sequence",
+	"invalid-tag",
+	"key-with-anchor-after-missing-explicit-mapping-value",
+	"leading-tab-content-in-literals/00",
+	"leading-tab-content-in-literals/01",
+	"leading-tabs-in-double-quoted/02",
+	"leading-tabs-in-double-quoted/05",
+	"legal-tab-after-indentation",
+	"literal-block-scalar-with-more-spaces-in-first-line",
+	"literal-modifers/00",
+	"literal-modifers/01",
+	"literal-modifers/02",
+	"literal-modifers/03",
+	"literal-scalars",
+	"mapping-key-and-flow-sequence-item-anchors",
+	"mapping-starting-at-line",
+	"mapping-with-anchor-on-document-start-line",
+	"missing-document-end-marker-before-directive",
+	"mixed-block-mapping-explicit-to-implicit",
+	"multiline-double-quoted-implicit-keys",
+	"multiline-plain-flow-mapping-key",
+	"multiline-plain-flow-mapping-key-without-value",
+	"multiline-plain-value-with-tabs-on-empty-lines",
+	"multiline-scalar-at-top-level",
+	"multiline-scalar-at-top-level-1-3",
+	"multiline-single-quoted-implicit-keys",
+	"multiline-unidented-double-quoted-block-key",
+	"nested-implicit-complex-keys",
+	"need-document-footer-before-directives",
+	"node-anchor-in-sequence",
+	"node-anchor-not-indented",
+	"plain-dashes-in-flow-sequence",
+	"plain-url-in-flow-mapping",
+	"question-mark-at-start-of-flow-key",
+	"question-mark-edge-cases/00",
+	"question-mark-edge-cases/01",
+	"scalar-doc-with-in-content/01",
+	"scalar-value-with-two-anchors",
+	"single-character-streams/01",
+	"single-pair-implicit-entries",
+	"spec-example-2-11-mapping-between-sequences",
+	"spec-example-6-12-separation-spaces",
+	"spec-example-7-16-flow-mapping-entries",
+	"spec-example-7-3-completely-empty-flow-nodes",
+	"spec-example-8-18-implicit-block-mapping-entries",
+	"spec-example-8-19-compact-block-mappings",
+	"spec-example-2-24-global-tags",
+	"spec-example-2-25-unordered-sets",
+	"spec-example-2-26-ordered-mappings",
+	"spec-example-5-12-tabs-and-spaces",
+	"spec-example-5-3-block-structure-indicators",
+	"spec-example-5-9-directive-indicator",
+	"spec-example-6-1-indentation-spaces",
+	"spec-example-6-13-reserved-directives",
+	"spec-example-6-19-secondary-tag-handle",
+	"spec-example-6-2-indentation-indicators",
+	"spec-example-6-21-local-tag-prefix",
+	"spec-example-6-23-node-properties",
+	"spec-example-6-24-verbatim-tags",
+	"spec-example-6-28-non-specific-tags",
+	"spec-example-6-3-separation-spaces",
+	"spec-example-6-4-line-prefixes",
+	"spec-example-6-6-line-folding",
+	"spec-example-6-6-line-folding-1-3",
+	"spec-example-6-7-block-folding",
+	"spec-example-6-8-flow-folding",
+	"spec-example-7-12-plain-lines",
+	"spec-example-7-19-single-pair-flow-mappings",
+	"spec-example-7-2-empty-content",
+	"spec-example-7-20-single-pair-explicit-entry",
+	"spec-example-7-24-flow-nodes",
+	"spec-example-7-6-double-quoted-lines",
+	"spec-example-7-9-single-quoted-lines",
+	"spec-example-8-10-folded-lines-8-13-final-empty-lines",
+	"spec-example-8-15-block-sequence-entry-types",
+	"spec-example-8-17-explicit-block-mapping-entries",
+	"spec-example-8-2-block-indentation-indicator",
+	"spec-example-8-22-block-collection-nodes",
+	"spec-example-8-7-literal-scalar",
+	"spec-example-8-7-literal-scalar-1-3",
+	"spec-example-8-8-literal-content",
+	"spec-example-9-3-bare-documents",
+	"spec-example-9-4-explicit-documents",
+	"spec-example-9-5-directives-documents",
+	"spec-example-9-6-stream",
+	"spec-example-9-6-stream-1-3",
+	"syntax-character-edge-cases/00",
+	"tab-at-beginning-of-line-followed-by-a-flow-mapping",
+	"tab-indented-top-flow",
+	"tabs-in-various-contexts/001",
+	"tabs-in-various-contexts/002",
+	"tabs-in-various-contexts/004",
+	"tabs-in-various-contexts/005",
+	"tabs-in-various-contexts/006",
+	"tabs-in-various-contexts/008",
+	"tabs-in-various-contexts/010",
+	"tabs-that-look-like-indentation/00",
+	"tabs-that-look-like-indentation/01",
+	"tabs-that-look-like-indentation/02",
+	"tabs-that-look-like-indentation/03",
+	"tabs-that-look-like-indentation/04",
+	"tabs-that-look-like-indentation/05",
+	"tabs-that-look-like-indentation/07",
+	"tabs-that-look-like-indentation/08",
+	"tags-for-block-objects",
+	"tags-for-flow-objects",
+	"tags-for-root-objects",
+	"tags-in-explicit-mapping",
+	"tags-in-implicit-mapping",
+	"tags-on-empty-scalars",
+	"three-dashes-and-content-without-space",
+	"trailing-line-of-spaces/01",                       // last '\n' character is needed ?
+	"various-combinations-of-explicit-block-mappings",  // no json
+	"various-trailing-comments",                        // no json
+	"various-trailing-comments-1-3",                    // no json
+	"wrong-indented-flow-sequence",                     // error ?
+	"wrong-indented-multiline-quoted-scalar",           // error ?
+	"zero-indented-sequences-in-explicit-mapping-keys", // no json
+}
+
+var failureTestNameMap map[string]struct{}
 
-func fatal(t *testing.T, msg string, args ...any) {
-	t.Helper()
-	if wip {
-		t.Logf(msg, args...)
-		return
+func init() {
+	failureTestNameMap = make(map[string]struct{})
+	for _, name := range failureTestNames {
+		failureTestNameMap[name] = struct{}{}
 	}
-	t.Fatalf(msg, args...)
 }
 
 func TestYAMLTestSuite(t *testing.T) {
@@ -33,28 +202,22 @@ func TestYAMLTestSuite(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	var (
-		success int
-		failure int
-	)
 	for _, test := range tests {
+		if _, exists := failureTestNameMap[test.Name]; exists {
+			continue
+		}
 		t.Run(test.Name, func(t *testing.T) {
 			defer func() {
 				if e := recover(); e != nil {
-					failure++
-					fatal(t, "panic occurred.\n[input]\n%s\nstack[%s]", string(test.InYAML), debug.Stack())
-					return
+					t.Fatalf("panic occurred.\n[input]\n%s\nstack[%s]", string(test.InYAML), debug.Stack())
 				}
 			}()
 
 			if test.Error {
 				var v any
 				if err := yaml.Unmarshal(test.InYAML, &v); err == nil {
-					failure++
-					fatal(t, "expected error.\n[input]\n%s\n", string(test.InYAML))
-					return
+					t.Fatalf("expected error.\n[input]\n%s\n", string(test.InYAML))
 				}
-				success++
 				return
 			}
 
@@ -66,41 +229,29 @@ func TestYAMLTestSuite(t *testing.T) {
 					if err == io.EOF {
 						break
 					}
-					failure++
-					fatal(t, err.Error()) //nolint: govet
-					return
+					t.Fatal(err)
 				}
 				if len(test.InJSON) <= idx {
-					failure++
-					fatal(t, "expected json value is nothing but got %v", v)
-					return
+					t.Fatalf("expected json value is nothing but got %v", v)
 				}
 				expected, err := json.Marshal(test.InJSON[idx])
 				if err != nil {
-					fatal(t, "failed to encode json value: %v", err)
-					return
+					t.Fatalf("failed to encode json value: %v", err)
 				}
 				got, err := json.Marshal(v)
 				if err != nil {
-					fatal(t, "failed to encode json value: %v", err)
-					return
+					t.Fatalf("failed to encode json value: %v", err)
 				}
 				if !bytes.Equal(expected, got) {
-					failure++
-					fatal(t, "json mismatch [%s]:\n[expected]\n%s\n[got]\n%s\n", test.Name, string(expected), string(got))
-					return
+					t.Fatalf("json mismatch [%s]:\n[expected]\n%s\n[got]\n%s\n", test.Name, string(expected), string(got))
 				}
 				idx++
 			}
-			success++
 		})
 	}
 	total := len(tests)
-	if success+failure == total {
-		t.Logf("yaml-test-suite result: success/total = %d/%d (%f %%)\n", success, total, float32(success)/float32(total)*100)
-	}
-	if success < successCountThreshold {
-		// degrade occurred.
-		t.Fatalf("expected success count is over %d but got %d", successCountThreshold, success)
-	}
+	failed := len(failureTestNames)
+	passed := total - failed
+	passedRate := float32(passed) / float32(total) * 100
+	t.Logf("total:[%d] passed:[%d] failure:[%d] passedRate:[%f%%]", total, passed, failed, passedRate)
 }