blob: ff2e3c96c0a864da87d4540e8d1b37e3bfeba0c1 [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"testing"
"time"
"dawn.googlesource.com/dawn/tools/src/container"
"dawn.googlesource.com/dawn/tools/src/oswrapper"
"github.com/stretchr/testify/require"
)
// invoke() and things that call it cannot be unittested due to the call to
// exec.CommandContext().
func TestExtractValidationHashes(t *testing.T) {
tests := []struct {
name string
input string
wantString string
wantHashes []string
}{
{
name: "No matches",
input: "ASDF",
wantString: "ASDF",
wantHashes: nil,
},
{
name: "Single match",
input: "Line 1\n<<HASH: ASDF>>\nLine 3",
wantString: "Line 1\nLine 3",
wantHashes: []string{"ASDF"},
},
{
name: "Multiple matches",
input: "Line 1\n<<HASH: ASDF>>\nLine 3\n<<HASH: QWER>>\nLine 5",
wantString: "Line 1\nLine 3\nLine 5",
wantHashes: []string{"ASDF", "QWER"},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
out, hashes := extractValidationHashes(testCase.input)
require.Equal(t, testCase.wantString, out)
require.Equal(t, testCase.wantHashes, hashes)
})
}
}
func TestIndent(t *testing.T) {
tests := []struct {
name string
input string
indentAmount int
want string
}{
{
name: "No indent single line",
input: "Foo",
indentAmount: 0,
want: "Foo",
},
{
name: "Indent single line",
input: "Foo",
indentAmount: 2,
want: " Foo",
},
{
name: "No indent multi line",
input: "Foo\nBar",
indentAmount: 0,
want: "Foo\nBar",
},
{
name: "Indent multi line",
input: "Foo\nBar",
indentAmount: 2,
want: " Foo\n Bar",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, indent(testCase.input, testCase.indentAmount))
})
}
}
func TestAlignLeft(t *testing.T) {
tests := []struct {
name string
input string
width int
want string
}{
{
name: "Width smaller than string length",
input: "Foo",
width: 2,
want: "Foo",
},
{
name: "Width equal to string length",
input: "Foo",
width: 3,
want: "Foo",
},
{
name: "Width greater than string length",
input: "Foo",
width: 5,
want: "Foo ",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, alignLeft(testCase.input, testCase.width))
})
}
}
func TestAlignCenter(t *testing.T) {
tests := []struct {
name string
input string
width int
want string
}{
{
name: "Width smaller than string length",
input: "Foo",
width: 2,
want: "Foo",
},
{
name: "Width equal to string length",
input: "Foo",
width: 3,
want: "Foo",
},
{
name: "Width greater than string length",
input: "Foo",
width: 5,
want: " Foo ",
},
{
name: "Width greater than string length non-equal padding",
input: "Foo",
width: 6,
want: " Foo ",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, alignCenter(testCase.input, testCase.width))
})
}
}
func TestAlignRight(t *testing.T) {
tests := []struct {
name string
input string
width int
want string
}{
{
name: "Width smaller than string length",
input: "Foo",
width: 2,
want: "Foo",
},
{
name: "Width equal to string length",
input: "Foo",
width: 3,
want: "Foo",
},
{
name: "Width greater than string length",
input: "Foo",
width: 5,
want: " Foo",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, alignRight(testCase.input, testCase.width))
})
}
}
func TestMaxStringLen(t *testing.T) {
tests := []struct {
name string
input []string
want int
}{
{
name: "Nil",
input: nil,
want: 0,
},
{
name: "Empty",
input: []string{},
want: 0,
},
{
name: "Multiple lines",
input: []string{
"Line 1",
"AAAAAAAAAAAA",
"Line 3",
},
want: 12,
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, maxStringLen(testCase.input))
})
}
}
func TestFormatWidth(t *testing.T) {
tests := []struct {
name string
input outputFormat
want int
}{
{
name: "Less than minimum",
input: "",
want: 6,
},
{
name: "Equal to minimum",
input: "123456",
want: 6,
},
{
name: "Greater than minimum",
input: "1234567890",
want: 10,
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, formatWidth(testCase.input))
})
}
}
func TestPercentage(t *testing.T) {
tests := []struct {
name string
inputN int
inputTotal int
want string
}{
{
name: "Zero total",
inputN: 10,
inputTotal: 0,
want: "-",
},
{
name: "Zero N",
inputN: 0,
inputTotal: 10,
want: "0.0%",
},
{
name: "Both positive",
inputN: 1,
inputTotal: 10,
want: "10.0%",
},
{
name: "Negative N",
inputN: -1,
inputTotal: 10,
want: "-10.0%",
},
{
name: "Negative total",
inputN: 1,
inputTotal: -10,
want: "-10.0%",
},
{
name: "Both negative",
inputN: -1,
inputTotal: -10,
want: "10.0%",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.want, percentage(testCase.inputN, testCase.inputTotal))
})
}
}
func TestParseFlags(t *testing.T) {
tests := []struct {
name string
path string
contents string
skipFileWrite bool
want []cmdLineFlags
wantErr bool
wantErrMsg string
}{
{
name: "Non-existent file",
path: "/foo.txt",
skipFileWrite: true,
want: nil,
wantErr: true,
wantErrMsg: "open /foo.txt: file does not exist",
},
{
name: "Single entry single flag no format",
path: "/foo.txt",
contents: `// Comment
// flags: --hlsl-shader-model 60
// Another comment
`,
want: []cmdLineFlags{
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--hlsl-shader-model", "60"},
},
},
},
{
name: "Single entry multiple flags no format",
path: "/foo.txt",
contents: `// Comment
// flags: --hlsl-shader-model 60 --overrides wgsize=10
// Another comment
`,
want: []cmdLineFlags{
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--hlsl-shader-model", "60", "--overrides", "wgsize=10"},
},
},
},
{
name: "Single entry single flag with format",
path: "/foo.txt",
contents: `// Comment
// [wgsl] flags: --hlsl-shader-model 60
// Another comment
`,
want: []cmdLineFlags{
{
formats: container.NewSet(wgsl),
flags: []string{"", "--hlsl-shader-model", "60"},
},
},
},
{
name: "Single entry multiple flags with format",
path: "/foo.txt",
contents: `// Comment
// [wgsl] flags: --hlsl-shader-model 60 --overrides wgsize=10
// Another comment
`,
want: []cmdLineFlags{
{
formats: container.NewSet(wgsl),
flags: []string{"", "--hlsl-shader-model", "60", "--overrides", "wgsize=10"},
},
},
},
{
name: "Multiple entries single flag no format",
path: "/foo.txt",
contents: `// Comment
// flags: --hlsl-shader-model 60
// Another comment
// flags: --overrides wgsize=10
`,
want: []cmdLineFlags{
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--hlsl-shader-model", "60"},
},
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--overrides", "wgsize=10"},
},
},
},
{
name: "Multiple entries multiple flags no format",
path: "/foo.txt",
contents: `// Comment
// flags: --hlsl-shader-model 60 --foo
// Another comment
// flags: --overrides wgsize=10 --bar
`,
want: []cmdLineFlags{
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--hlsl-shader-model", "60", "--foo"},
},
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--overrides", "wgsize=10", "--bar"},
},
},
},
{
name: "Multiple entries single flag with format",
path: "/foo.txt",
contents: `// Comment
// [wgsl] flags: --hlsl-shader-model 60
// Another comment
// [glsl] flags: --overrides wgsize=10
`,
want: []cmdLineFlags{
{
formats: container.NewSet(wgsl),
flags: []string{"", "--hlsl-shader-model", "60"},
},
{
formats: container.NewSet(glsl),
flags: []string{"", "--overrides", "wgsize=10"},
},
},
},
{
name: "Multiple entries multiple flags with format",
path: "/foo.txt",
contents: `// Comment
// [wgsl] flags: --hlsl-shader-model 60 --foo
// Another comment
// [glsl] flags: --overrides wgsize=10 --bar
`,
want: []cmdLineFlags{
{
formats: container.NewSet(wgsl),
flags: []string{"", "--hlsl-shader-model", "60", "--foo"},
},
{
formats: container.NewSet(glsl),
flags: []string{"", "--overrides", "wgsize=10", "--bar"},
},
},
},
{
name: "Multiple entries multiple flags mixed formats",
path: "/foo.txt",
contents: `// Comment
// flags: --hlsl-shader-model 60 --foo
// Another comment
// [glsl] flags: --overrides wgsize=10 --bar
`,
want: []cmdLineFlags{
{
formats: container.NewSet(allOutputFormats...),
flags: []string{"", "--hlsl-shader-model", "60", "--foo"},
},
{
formats: container.NewSet(glsl),
flags: []string{"", "--overrides", "wgsize=10", "--bar"},
},
},
},
{
name: "Invalid format",
path: "/foo.txt",
contents: `// Comment
// [invalid] flags: --hlsl-shader-model 60
// Another comment
`,
want: nil,
wantErr: true,
wantErrMsg: "unknown format 'invalid'",
},
{
name: "-ir formats automatically included",
path: "/foo.txt",
contents: `// Comment
// [hlsl-dxc] flags: --hlsl-shader-model 60
// Another comment
`,
want: []cmdLineFlags{
{
formats: container.NewSet(hlslDXC),
flags: []string{"", "--hlsl-shader-model", "60"},
},
},
},
{
name: "Ignored after first comment block",
path: "/foo.txt",
contents: `// Comment
// [wgsl] flags: --hlsl-shader-model 60
// Another comment
foo
// [glsl] flags: --hlsl-shader-model 60
`,
want: []cmdLineFlags{
{
formats: container.NewSet(wgsl),
flags: []string{"", "--hlsl-shader-model", "60"},
},
},
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
wrapper := oswrapper.CreateMemMapOSWrapper()
if !testCase.skipFileWrite {
wrapper.WriteFile(testCase.path, []byte(testCase.contents), 0o700)
}
flags, err := parseFlags(testCase.path, wrapper)
if testCase.wantErr {
require.ErrorContains(t, err, testCase.wantErrMsg)
} else {
require.NoErrorf(t, err, "Got error parsing flags: %v", err)
}
require.Equal(t, testCase.want, flags)
})
}
}
func TestPrintDuration(t *testing.T) {
tests := []struct {
name string
inputToParse string
want string
}{
{
name: "Seconds",
inputToParse: "5s",
want: "5s",
},
{
name: "Minutes",
inputToParse: "5m",
want: "5m",
},
{
name: "Minutes and Seconds",
inputToParse: "5m5s",
want: "5m5s",
},
{
name: "Hours",
inputToParse: "5h",
want: "5h",
},
{
name: "Hours and Minutes",
inputToParse: "5h5m",
want: "5h5m",
},
{
name: "Hours and Seconds",
inputToParse: "5h5s",
want: "5h5s",
},
{
name: "Hours, Minutes, and Seconds",
inputToParse: "5h5m5s",
want: "5h5m5s",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
input, err := time.ParseDuration(testCase.inputToParse)
require.NoErrorf(t, err, "Failed to parse duration: %v", err)
require.Equal(t, testCase.want, printDuration(input))
})
}
}