github.com/KEINOS/go-countline@v1.1.1-0.20221217083629-60710df7606b/cl/spec/spec.go (about) 1 /* 2 Package spec provides the test specifications for the CountLines function. 3 4 Alternate implementations of CountLines function must pass the test as well. 5 */ 6 package spec 7 8 import ( 9 "bufio" 10 "fmt" 11 "io" 12 "strings" 13 "testing" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 "github.com/zenizh/go-capturer" 18 ) 19 20 // ============================================================================ 21 // Data Provider of CountLines Specification 22 // ============================================================================ 23 24 // DataCountLines is the data provider for the CountLines function to check if 25 // the specifications are covered. 26 // Alternate functions must pass the test with this data as well. 27 // 28 //nolint:gomnd,gochecknoglobals // numbers of ExpectOut are not magic numbers and let DataCountLines be global. 29 var DataCountLines = []struct { 30 Reason string // Reason on failure 31 Input string // Input data 32 ExpectOut int // Expected output 33 }{ 34 { 35 Reason: "'<EOF>' --> empty file should be zero", 36 Input: "", 37 ExpectOut: 0, 38 }, 39 { 40 Reason: "'Hello<EOF>' --> single line without line break should be one", 41 Input: "Hello", 42 ExpectOut: 1, 43 }, 44 { 45 Reason: "'Hello\\n<EOF>' --> single line with line break should be one", 46 Input: "Hello\n", 47 ExpectOut: 1, 48 }, 49 { 50 Reason: "'\\n<EOF>' --> single line break should be one", 51 Input: "\n", 52 ExpectOut: 1, 53 }, 54 { 55 Reason: "'\\n\\n<EOF>' --> two line breaks should be two", 56 Input: "\n\n", 57 ExpectOut: 2, 58 }, 59 { 60 Reason: "'\\nHello<EOF>' --> one line break and one line without line break should be two", 61 Input: "\nHello", 62 ExpectOut: 2, 63 }, 64 { 65 Reason: "'\\nHello\\n<EOF>' --> one line break and one line with line break should be two", 66 Input: "\nHello\n", 67 ExpectOut: 2, 68 }, 69 { 70 Reason: "'\\n\\nHello<EOF>' --> two line breaks and one line without line break should be three", 71 Input: "\n\nHello", 72 ExpectOut: 3, 73 }, 74 { 75 Reason: "'\\n\\nHello\\n<EOF>' --> two line breaks and one line with line break should be three", 76 Input: "\n\nHello\n", 77 ExpectOut: 3, 78 }, 79 { 80 Reason: "'<large line>\\n<EOF>' --> long string with a line break should be onw", 81 Input: GetStrDummyLines(bufio.MaxScanTokenSize*2, 1), 82 ExpectOut: 1, 83 }, 84 { 85 Reason: "'<large line>\\n<large line>\\n<EOF>' --> long string but two lines should be two", 86 Input: GetStrDummyLines(bufio.MaxScanTokenSize*2, 2), 87 ExpectOut: 2, 88 }, 89 } 90 91 // ============================================================================ 92 // Functions 93 // ============================================================================ 94 95 // ---------------------------------------------------------------------------- 96 // RunSpecTest 97 // ---------------------------------------------------------------------------- 98 99 // RunSpecTest is a helper function to run the specifcations of LineCount function. 100 // Alternate implementations (_alt.*) must pass this test as well. 101 // 102 //nolint:varnamelen // fn is short for the scope of its usage but leave it as is. 103 func RunSpecTest(t *testing.T, nameFn string, fn func(io.Reader) (int, error)) { 104 t.Helper() 105 106 const threshold = 1024 // Max size of input data to begin cropping 107 108 for index, test := range DataCountLines { 109 testNum := fmt.Sprintf("test #%v", index) 110 111 t.Run(testNum, func(t *testing.T) { 112 logInput := fmt.Sprintf("%v", test.Input) 113 114 // Crop the input to make it readable 115 if len(test.Input) > threshold { 116 logInput = fmt.Sprintf("%v", test.Input[:64]+"..."+test.Input[len(test.Input)-64:]) 117 } 118 119 t.Logf("Input : %#v", logInput) 120 t.Log("Input len:", len(test.Input)) 121 122 ioReader := strings.NewReader(test.Input) 123 124 msgReason := fmt.Sprintf("%v %v: %v", nameFn, testNum, test.Reason) 125 expect := test.ExpectOut 126 127 // Run the target function. Capture the STDOUT and STDERR as well. 128 out := capturer.CaptureOutput(func() { 129 actual, err := fn(ioReader) 130 131 require.NoError(t, err, "golden case should not return error") 132 assert.Equal(t, expect, actual, msgReason) 133 }) 134 135 assert.Emptyf(t, out, 136 "%v %v: it should not output to STDOUT/STDERR on success.\nOut: %v", 137 nameFn, testNum, out, 138 ) 139 }) 140 } 141 } 142 143 // ---------------------------------------------------------------------------- 144 // GetStrDummyLines 145 // ---------------------------------------------------------------------------- 146 147 // GetStrDummyLines returns a string with the given size and number of lines. 148 // 'sizeLine' is the size of each line. 'numLines' is the number of lines. 149 func GetStrDummyLines(sizeLine, numLine int64) string { 150 dataLine := genOneLine(sizeLine) 151 152 result := "" 153 for i := int64(0); i < numLine; i++ { 154 result += string(dataLine) 155 } 156 157 return result 158 } 159 160 // ---------------------------------------------------------------------------- 161 // genOneLine 162 // ---------------------------------------------------------------------------- 163 164 func genOneLine(sizeLine int64) []byte { 165 if sizeLine <= 0 { 166 return []byte{} 167 } 168 169 const LF = byte(0x0a) //nolint:varnamelen 170 171 dataLine := make([]byte, sizeLine) 172 173 for i := int64(0); i < sizeLine; i++ { 174 dataLine[i] = 'a' 175 176 if i == sizeLine-1 { 177 dataLine[i] = LF // \n 178 } 179 } 180 181 return dataLine 182 }