Repository: gohugoio/hugo (language: go)

Issue:
Improve attribute vs options in new code blocks setup
Me and @jmooring had a discussion about "namespacing" for the code language to distinguish between Chroma lexers/languages and custom code hooks. I suggest we let this be up to the user. Partly because GitHub seems to have chosen a flat namespace for this (e.g `mermaid` diagrams), secondly, it adds complexity that we need to explain to people and removes potential flexibility.

But ...

I think we should improve the attribute handling a little. Currently, we have a set of reserved options that we put in a separate map (`.Options`), see below. But some of them are rather generic (e.g. `style`) and may be likely to be used by "custom hooks".

So what I suggest would be to:

1. Check if `language` is a known Chroma lexer.
2. If yes, put the reserved attributes in `.Options`.
3. Else, put all in `.Attributes`.


```go
// Markdown attributes used as options by the Chroma highlighter.
var chromaHightlightProcessingAttributes = map[string]bool{
	"anchorLineNos":      true,
	"guessSyntax":        true,
	"hl_Lines":           true,
	"lineAnchors":        true,
	"lineNos":            true,
	"lineNoStart":        true,
	"lineNumbersInTable": true,
	"noClasses":          true,
	"nohl":               true,
	"style":              true,
	"tabWidth":           true,
}

```


Discussion / hints:
_For future reference only. I am not suggesting that we change anything._

The namespace train left the station a long time ago. Yet another seven-year [CommonMark discussion](<https://talk.commonmark.org/t/mermaid-generation-of-diagrams-and-flowcharts-from-text-in-a-similar-manner-as-markdown/1882>) lingered without resolution. Others took the lead, so we have a de facto standard as implemented by:
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts) in 2017
- [Gitea](https://gitea.io/) in 2020
- [JetBrains](https://www.jetbrains.com/help/idea/markdown.html#diagrams) in 2020
- [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) in 2022
- This [Microsoft Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) in 2017
- And others...

@bep Can you please hold off on this change for 24 hours? 
Yea, I can even wait until Monday ...
I have a few other things to get into the next Hugo release (which will be surprisingly useful), which would be likely to be done thursdayish ...
Please proceed with this. Without this change things become a bit more difficult. Thanks.

Fix patch:
diff --git a/common/text/transform.go b/common/text/transform.go
index f5957780363..66a67d8bc1a 100644
--- a/common/text/transform.go
+++ b/common/text/transform.go
@@ -14,6 +14,7 @@
 package text
 
 import (
+	"strings"
 	"sync"
 	"unicode"
 
@@ -45,3 +46,18 @@ func RemoveAccentsString(s string) string {
 	accentTransformerPool.Put(t)
 	return s
 }
+
+// Chomp removes trailing newline characters from s.
+func Chomp(s string) string {
+	return strings.TrimRightFunc(s, func(r rune) bool {
+		return r == '\n' || r == '\r'
+	})
+}
+
+// Puts adds a trailing \n none found.
+func Puts(s string) string {
+	if s == "" || s[len(s)-1] == '\n' {
+		return s
+	}
+	return s + "\n"
+}
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index 29beb672e55..4067e827faf 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -23,6 +23,7 @@ import (
 	"sync"
 	"unicode/utf8"
 
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/identity"
 	"github.com/mitchellh/mapstructure"
 	"github.com/pkg/errors"
@@ -430,6 +431,25 @@ func (p *pageContentOutput) initRenderHooks() error {
 		renderCache := make(map[cacheKey]interface{})
 		var renderCacheMu sync.Mutex
 
+		resolvePosition := func(ctx interface{}) text.Position {
+			var offset int
+
+			switch v := ctx.(type) {
+			case hooks.CodeblockContext:
+				offset = bytes.Index(p.p.source.parsed.Input(), []byte(v.Code()))
+			}
+
+			pos := p.p.posFromInput(p.p.source.parsed.Input(), offset)
+
+			if pos.LineNumber > 0 {
+				// Move up to the code fence delimiter.
+				// This is in line with how we report on shortcodes.
+				pos.LineNumber = pos.LineNumber - 1
+			}
+
+			return pos
+		}
+
 		p.renderHooks.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} {
 			renderCacheMu.Lock()
 			defer renderCacheMu.Unlock()
@@ -510,6 +530,7 @@ func (p *pageContentOutput) initRenderHooks() error {
 				templateHandler: p.p.s.Tmpl(),
 				SearchProvider:  templ.(identity.SearchProvider),
 				templ:           templ,
+				resolvePosition: resolvePosition,
 			}
 			renderCache[key] = r
 			return r
diff --git a/hugolib/site.go b/hugolib/site.go
index d78a4e10c9c..c76bdc141bc 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -1778,7 +1778,8 @@ var infoOnMissingLayout = map[string]bool{
 type hookRendererTemplate struct {
 	templateHandler tpl.TemplateHandler
 	identity.SearchProvider
-	templ tpl.Template
+	templ           tpl.Template
+	resolvePosition func(ctx interface{}) text.Position
 }
 
 func (hr hookRendererTemplate) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
@@ -1793,6 +1794,14 @@ func (hr hookRendererTemplate) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Co
 	return hr.templateHandler.Execute(hr.templ, w, ctx)
 }
 
+func (hr hookRendererTemplate) ResolvePosition(ctx interface{}) text.Position {
+	return hr.resolvePosition(ctx)
+}
+
+func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool {
+	return false
+}
+
 func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) {
 	if templ == nil {
 		s.logMissingLayout(name, "", "", outputFormat)
diff --git a/markup/converter/converter.go b/markup/converter/converter.go
index 30addfec657..dac0a6b73f9 100644
--- a/markup/converter/converter.go
+++ b/markup/converter/converter.go
@@ -128,9 +128,13 @@ type DocumentContext struct {
 
 // RenderContext holds contextual information about the content to render.
 type RenderContext struct {
-	Src       []byte
+	// Src is the content to render.
+	Src []byte
+
+	// Whether to render TableOfContents.
 	RenderTOC bool
 
+	// GerRenderer provides hook renderers on demand.
 	GetRenderer hooks.GetRendererFunc
 }
 
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
index 987cb1dc36b..e81689f0435 100644
--- a/markup/converter/hooks/hooks.go
+++ b/markup/converter/hooks/hooks.go
@@ -17,6 +17,7 @@ import (
 	"io"
 
 	"github.com/gohugoio/hugo/common/hugio"
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
 )
@@ -37,6 +38,7 @@ type LinkContext interface {
 
 type CodeblockContext interface {
 	AttributesProvider
+	text.Positioner
 	Options() map[string]interface{}
 	Lang() string
 	Code() string
@@ -59,6 +61,10 @@ type CodeBlockRenderer interface {
 	identity.Provider
 }
 
+type IsDefaultCodeBlockRendererProvider interface {
+	IsDefaultCodeBlockRenderer() bool
+}
+
 // HeadingContext contains accessors to all attributes that a HeadingRenderer
 // can use to render a heading.
 type HeadingContext interface {
@@ -84,6 +90,14 @@ type HeadingRenderer interface {
 	identity.Provider
 }
 
+// ElementPositionResolver provides a way to resolve the start Position
+// of a markdown element in the original source document.
+// This may be both slow and aproximate, so should only be
+// used for error logging.
+type ElementPositionResolver interface {
+	ResolvePosition(ctx interface{}) text.Position
+}
+
 type RendererType int
 
 const (
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
index 59d142e23d0..27252fc27cf 100644
--- a/markup/goldmark/codeblocks/render.go
+++ b/markup/goldmark/codeblocks/render.go
@@ -16,7 +16,10 @@ package codeblocks
 import (
 	"bytes"
 	"fmt"
+	"sync"
 
+	"github.com/alecthomas/chroma/lexers"
+	htext "github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/markup/converter/hooks"
 	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
@@ -29,15 +32,15 @@ import (
 )
 
 type (
-	diagrams     struct{}
-	htmlRenderer struct{}
+	codeBlocksExtension struct{}
+	htmlRenderer        struct{}
 )
 
 func New() goldmark.Extender {
-	return &diagrams{}
+	return &codeBlocksExtension{}
 }
 
-func (e *diagrams) Extend(m goldmark.Markdown) {
+func (e *codeBlocksExtension) Extend(m goldmark.Markdown) {
 	m.Parser().AddOptions(
 		parser.WithASTTransformers(
 			util.Prioritized(&Transformer{}, 100),
@@ -66,6 +69,11 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
 
 	n := node.(*codeBlock)
 	lang := string(n.b.Language(src))
+	renderer := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
+	if renderer == nil {
+		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
+	}
+
 	ordinal := n.ordinal
 
 	var buff bytes.Buffer
@@ -75,30 +83,47 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
 		line := n.b.Lines().At(i)
 		buff.Write(line.Value(src))
 	}
-	text := buff.String()
+
+	s := htext.Chomp(buff.String())
 
 	var info []byte
 	if n.b.Info != nil {
 		info = n.b.Info.Segment.Value(src)
 	}
+
+	attrtp := attributes.AttributesOwnerCodeBlockCustom
+	if isd, ok := renderer.(hooks.IsDefaultCodeBlockRendererProvider); (ok && isd.IsDefaultCodeBlockRenderer()) || lexers.Get(lang) != nil {
+		// We say that this is a Chroma code block if it's the default code block renderer
+		// or if the language is supported by Chroma.
+		attrtp = attributes.AttributesOwnerCodeBlockChroma
+	}
+
+	// IsDefaultCodeBlockRendererProvider
 	attrs := getAttributes(n.b, info)
+	cbctx := &codeBlockContext{
+		page:             ctx.DocumentContext().Document,
+		lang:             lang,
+		code:             s,
+		ordinal:          ordinal,
+		AttributesHolder: attributes.New(attrs, attrtp),
+	}
 
-	v := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
-	if v == nil {
-		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
+	cbctx.createPos = func() htext.Position {
+		if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
+			return resolver.ResolvePosition(cbctx)
+		}
+		return htext.Position{
+			Filename:     ctx.DocumentContext().Filename,
+			LineNumber:   0,
+			ColumnNumber: 0,
+		}
 	}
 
-	cr := v.(hooks.CodeBlockRenderer)
+	cr := renderer.(hooks.CodeBlockRenderer)
 
 	err := cr.RenderCodeblock(
 		w,
-		codeBlockContext{
-			page:             ctx.DocumentContext().Document,
-			lang:             lang,
-			code:             text,
-			ordinal:          ordinal,
-			AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
-		},
+		cbctx,
 	)
 
 	ctx.AddIdentity(cr)
@@ -111,25 +136,39 @@ type codeBlockContext struct {
 	lang    string
 	code    string
 	ordinal int
+
+	// This is only used in error situations and is expensive to create,
+	// to deleay creation until needed.
+	pos       htext.Position
+	posInit   sync.Once
+	createPos func() htext.Position
+
 	*attributes.AttributesHolder
 }
 
-func (c codeBlockContext) Page() interface{} {
+func (c *codeBlockContext) Page() interface{} {
 	return c.page
 }
 
-func (c codeBlockContext) Lang() string {
+func (c *codeBlockContext) Lang() string {
 	return c.lang
 }
 
-func (c codeBlockContext) Code() string {
+func (c *codeBlockContext) Code() string {
 	return c.code
 }
 
-func (c codeBlockContext) Ordinal() int {
+func (c *codeBlockContext) Ordinal() int {
 	return c.ordinal
 }
 
+func (c *codeBlockContext) Position() htext.Position {
+	c.posInit.Do(func() {
+		c.pos = c.createPos()
+	})
+	return c.pos
+}
+
 func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
 	if node.Attributes() != nil {
 		return node.Attributes()
diff --git a/markup/goldmark/codeblocks/transform.go b/markup/goldmark/codeblocks/transform.go
index 791e99a5c3c..be5334b5f90 100644
--- a/markup/goldmark/codeblocks/transform.go
+++ b/markup/goldmark/codeblocks/transform.go
@@ -40,6 +40,7 @@ func (*Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser
 		}
 
 		codeBlocks = append(codeBlocks, cb)
+
 		return ast.WalkContinue, nil
 	})
 
diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go
index 6013ba1c8b1..dac45e3121b 100644
--- a/markup/highlight/highlight.go
+++ b/markup/highlight/highlight.go
@@ -25,6 +25,7 @@ import (
 	"github.com/alecthomas/chroma/lexers"
 	"github.com/alecthomas/chroma/styles"
 	"github.com/gohugoio/hugo/common/hugio"
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/markup/converter/hooks"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
@@ -60,6 +61,7 @@ type Highlighter interface {
 	Highlight(code, lang string, opts interface{}) (string, error)
 	HighlightCodeBlock(ctx hooks.CodeblockContext, opts interface{}) (HightlightResult, error)
 	hooks.CodeBlockRenderer
+	hooks.IsDefaultCodeBlockRendererProvider
 }
 
 type chromaHighlighter struct {
@@ -123,7 +125,13 @@ func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Codebl
 		return err
 	}
 
-	return highlight(w, ctx.Code(), ctx.Lang(), attributes, cfg)
+	code := text.Puts(ctx.Code())
+
+	return highlight(w, code, ctx.Lang(), attributes, cfg)
+}
+
+func (h chromaHighlighter) IsDefaultCodeBlockRenderer() bool {
+	return true
 }
 
 var id = identity.NewPathIdentity("chroma", "highlight")
diff --git a/markup/internal/attributes/attributes.go b/markup/internal/attributes/attributes.go
index 1cce7edd1e0..a20690c4cc6 100644
--- a/markup/internal/attributes/attributes.go
+++ b/markup/internal/attributes/attributes.go
@@ -50,7 +50,8 @@ type AttributesOwnerType int
 
 const (
 	AttributesOwnerGeneral AttributesOwnerType = iota
-	AttributesOwnerCodeBlock
+	AttributesOwnerCodeBlockChroma
+	AttributesOwnerCodeBlockCustom
 )
 
 func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *AttributesHolder {
@@ -99,7 +100,7 @@ func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *Attribut
 			panic(fmt.Sprintf("not implemented: %T", vvv))
 		}
 
-		if ownerType == AttributesOwnerCodeBlock && chromaHightlightProcessingAttributes[nameLower] {
+		if ownerType == AttributesOwnerCodeBlockChroma && chromaHightlightProcessingAttributes[nameLower] {
 			attr := Attribute{Name: string(v.Name), Value: vv}
 			opts = append(opts, attr)
 		} else {
diff --git a/tpl/strings/strings.go b/tpl/strings/strings.go
index ac2defed50d..2575b2feee3 100644
--- a/tpl/strings/strings.go
+++ b/tpl/strings/strings.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 	"unicode/utf8"
 
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/deps"
 	"github.com/gohugoio/hugo/helpers"
 
@@ -119,7 +120,7 @@ func (ns *Namespace) Chomp(s interface{}) (interface{}, error) {
 		return "", err
 	}
 
-	res := strings.TrimRight(ss, "\r\n")
+	res := text.Chomp(ss)
 	switch s.(type) {
 	case template.HTML:
 		return template.HTML(res), nil


Test patch:
diff --git a/common/text/transform_test.go b/common/text/transform_test.go
index 08265f976b5..992dd524c80 100644
--- a/common/text/transform_test.go
+++ b/common/text/transform_test.go
@@ -26,3 +26,18 @@ func TestRemoveAccents(t *testing.T) {
 	c.Assert(string(RemoveAccents([]byte("Hugo Rocks!"))), qt.Equals, "Hugo Rocks!")
 	c.Assert(string(RemoveAccentsString("Resumé")), qt.Equals, "Resume")
 }
+
+func TestChomp(t *testing.T) {
+	c := qt.New(t)
+
+	c.Assert(Chomp("\nA\n"), qt.Equals, "\nA")
+	c.Assert(Chomp("A\r\n"), qt.Equals, "A")
+}
+
+func TestPuts(t *testing.T) {
+	c := qt.New(t)
+
+	c.Assert(Puts("A"), qt.Equals, "A\n")
+	c.Assert(Puts("\nA\n"), qt.Equals, "\nA\n")
+	c.Assert(Puts(""), qt.Equals, "")
+}
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index ed68783a165..b7b43624f37 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -28,6 +28,9 @@ import (
 )
 
 func NewIntegrationTestBuilder(conf IntegrationTestConfig) *IntegrationTestBuilder {
+	// Code fences.
+	conf.TxtarString = strings.ReplaceAll(conf.TxtarString, "§§§", "```")
+
 	data := txtar.Parse([]byte(conf.TxtarString))
 
 	c, ok := conf.T.(*qt.C)
diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go
index d662b39118e..001deb9f8dd 100644
--- a/markup/goldmark/codeblocks/integration_test.go
+++ b/markup/goldmark/codeblocks/integration_test.go
@@ -14,6 +14,7 @@
 package codeblocks_test
 
 import (
+	"path/filepath"
 	"strings"
 	"testing"
 
@@ -54,25 +55,25 @@ title: "p1"
 
 ## Ascii Diagram
 
-CODE_FENCEgoat { width="600" }
+§§§goat { width="600" }
 --->
-CODE_FENCE
+§§§
 
 ## Go Code
 
-CODE_FENCEgo
+§§§go
 fmt.Println("Hello, World!");
-CODE_FENCE
+§§§
 
 ## Golang Code
 
-CODE_FENCEgolang
+§§§golang
 fmt.Println("Hello, Golang!");
-CODE_FENCE
+§§§
 
 ## Bash Code
 
-CODE_FENCEbash { linenos=inline,hl_lines=[2,"5-6"],linenostart=32 class=blue }
+§§§bash { linenos=inline,hl_lines=[2,"5-6"],linenostart=32 class=blue }
 echo "l1";
 echo "l2";
 echo "l3";
@@ -81,11 +82,9 @@ echo "l5";
 echo "l6";
 echo "l7";
 echo "l8";
-CODE_FENCE
+§§§
 `
 
-	files = strings.ReplaceAll(files, "CODE_FENCE", "```")
-
 	b := hugolib.NewIntegrationTestBuilder(
 		hugolib.IntegrationTestConfig{
 			T:           t,
@@ -113,3 +112,105 @@ Go Language: golang|
 		"<h2 id=\"bash-code\">Bash Code</h2>\n<div class=\"highlight blue\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"ln\">32</span><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">&#34;l1&#34;</span><span class=\"p\">;</span>\n</span></span><span class=\"line hl\"><span class=\"ln\">33</span>",
 	)
 }
+
+func TestCodeChomp(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+§§§bash
+echo "p1";
+§§§
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+|{{ .Code | safeHTML }}|
+
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			NeedsOsFS:   false,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", "|echo \"p1\";|")
+}
+
+func TestCodePosition(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+##   Code
+
+§§§
+echo "p1";
+§§§
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+Position: {{ .Position | safeHTML }}
+
+
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"content/p1.md:7:1\""))
+}
+
+// Issue 9571
+func TestAttributesChroma(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+##   Code
+
+§§§LANGUAGE {style=monokai}
+echo "p1";
+§§§
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+Attributes: {{ .Attributes }}|Options: {{ .Options }}|
+
+
+`
+	testLanguage := func(language, expect string) {
+		b := hugolib.NewIntegrationTestBuilder(
+			hugolib.IntegrationTestConfig{
+				T:           t,
+				TxtarString: strings.ReplaceAll(files, "LANGUAGE", language),
+			},
+		).Build()
+
+		b.AssertFileContent("public/p1/index.html", expect)
+	}
+
+	testLanguage("bash", "Attributes: map[]|Options: map[style:monokai]|")
+	testLanguage("hugo", "Attributes: map[style:monokai]|Options: map[]|")
+}
diff --git a/markup/goldmark/integration_test.go b/markup/goldmark/integration_test.go
index f1fa745c5ae..16705ad6290 100644
--- a/markup/goldmark/integration_test.go
+++ b/markup/goldmark/integration_test.go
@@ -250,9 +250,6 @@ LINE8
 
 `
 
-	// Code fences
-	files = strings.ReplaceAll(files, "§§§", "```")
-
 	b := hugolib.NewIntegrationTestBuilder(
 		hugolib.IntegrationTestConfig{
 			T:           t,