github.com/aretext/aretext@v1.3.0/syntax/languages/makefile_test.go (about) 1 package languages 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 8 "github.com/aretext/aretext/syntax/parser" 9 ) 10 11 func TestMakefileParseFunc(t *testing.T) { 12 testCases := []struct { 13 name string 14 text string 15 expected []TokenWithText 16 }{ 17 { 18 name: "comment", 19 text: `# comment`, 20 expected: []TokenWithText{ 21 {Text: `# comment`, Role: parser.TokenRoleComment}, 22 }, 23 }, 24 { 25 name: "comment with colon", 26 text: `# notatarget: xyz`, 27 expected: []TokenWithText{ 28 {Text: `# notatarget: xyz`, Role: parser.TokenRoleComment}, 29 }, 30 }, 31 { 32 name: "rule", 33 text: ` 34 foo: bar.c 35 cc bar.c -o foo 36 37 bar.c: 38 echo "test" > bar.c 39 `, 40 expected: []TokenWithText{}, 41 }, 42 { 43 name: "variable assignment", 44 text: ` 45 CC := gcc 46 x ?= maybe 47 y = foobar 48 out != echo "hello" 49 `, 50 expected: []TokenWithText{ 51 {Text: `:=`, Role: parser.TokenRoleOperator}, 52 {Text: `?=`, Role: parser.TokenRoleOperator}, 53 {Text: `=`, Role: parser.TokenRoleOperator}, 54 {Text: `!=`, Role: parser.TokenRoleOperator}, 55 }, 56 }, 57 { 58 name: "variable assignment with %", 59 text: "BUILD_DATE := `date +%FT%T%z`", 60 expected: []TokenWithText{ 61 {Text: `:=`, Role: parser.TokenRoleOperator}, 62 }, 63 }, 64 { 65 name: "variable assignment with backslash line continuation", 66 text: ` 67 VAR = abc \ 68 % \ 69 xyz 70 71 %.o: %.c 72 $(CC) -o $@ 73 `, 74 expected: []TokenWithText{ 75 {Text: "=", Role: parser.TokenRoleOperator}, 76 {Text: "%", Role: makefileTokenRolePattern}, 77 {Text: "%", Role: makefileTokenRolePattern}, 78 {Text: "$(CC)", Role: makefileTokenRoleVariable}, 79 {Text: "$@", Role: makefileTokenRoleVariable}, 80 }, 81 }, 82 { 83 name: "command with colon", 84 text: ` 85 test: 86 echo "foo:bar" 87 `, 88 expected: []TokenWithText{}, 89 }, 90 { 91 name: "variable assignment followed by colon", 92 text: ` 93 ARETEXT_URL ?= https://aretext.org 94 `, 95 expected: []TokenWithText{ 96 {Text: `?=`, Role: parser.TokenRoleOperator}, 97 }, 98 }, 99 { 100 name: "variable assignment with expansion", 101 text: `COMMIT := $(shell git rev-parse --short HEAD)`, 102 expected: []TokenWithText{ 103 {Text: `:=`, Role: parser.TokenRoleOperator}, 104 {Text: `$(shell git rev-parse --short HEAD)`, Role: makefileTokenRoleVariable}, 105 }, 106 }, 107 { 108 name: "variable with nested expansions", 109 text: `$(${$(VAR)} abc)`, 110 expected: []TokenWithText{ 111 {Text: `$(${$(VAR)} abc)`, Role: makefileTokenRoleVariable}, 112 }, 113 }, 114 { 115 name: "rule with variables", 116 text: ` 117 objects = program.o foo.o utils.o 118 program : $(objects) 119 cc -o program $(objects) 120 121 $(objects) : defs.h 122 `, 123 expected: []TokenWithText{ 124 {Text: "=", Role: parser.TokenRoleOperator}, 125 {Text: "$(objects)", Role: makefileTokenRoleVariable}, 126 {Text: "$(objects)", Role: makefileTokenRoleVariable}, 127 {Text: "$(objects)", Role: makefileTokenRoleVariable}, 128 }, 129 }, 130 { 131 name: "rule with automatic variables", 132 text: ` 133 %.o: %.c 134 $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 135 `, 136 expected: []TokenWithText{ 137 {Text: "%", Role: makefileTokenRolePattern}, 138 {Text: "%", Role: makefileTokenRolePattern}, 139 {Text: "$(CC)", Role: makefileTokenRoleVariable}, 140 {Text: "$(CFLAGS)", Role: makefileTokenRoleVariable}, 141 {Text: "$(CPPFLAGS)", Role: makefileTokenRoleVariable}, 142 {Text: "$<", Role: makefileTokenRoleVariable}, 143 {Text: "$@", Role: makefileTokenRoleVariable}, 144 }, 145 }, 146 { 147 name: "escaped dollar sign", 148 text: ` 149 foo: foo.c 150 echo "$${SHELL}" 151 `, 152 expected: []TokenWithText{}, 153 }, 154 { 155 name: "@ suppress output in command", 156 text: ` 157 foo: foo.c 158 @ echo "test" 159 @echo "test2" 160 `, 161 expected: []TokenWithText{ 162 {Text: "@", Role: parser.TokenRoleOperator}, 163 {Text: "@", Role: parser.TokenRoleOperator}, 164 }, 165 }, 166 { 167 name: "conditional at top-level", 168 text: ` 169 ifeq ($(CC),gcc) 170 libs=$(libs_for_gcc) 171 else 172 libs=$(normal_libs) 173 endif 174 `, 175 expected: []TokenWithText{ 176 {Text: "ifeq", Role: parser.TokenRoleKeyword}, 177 {Text: "$(CC)", Role: makefileTokenRoleVariable}, 178 {Text: "=", Role: parser.TokenRoleOperator}, 179 {Text: "$(libs_for_gcc)", Role: makefileTokenRoleVariable}, 180 {Text: "else", Role: parser.TokenRoleKeyword}, 181 {Text: "=", Role: parser.TokenRoleOperator}, 182 {Text: "$(normal_libs)", Role: makefileTokenRoleVariable}, 183 {Text: "endif", Role: parser.TokenRoleKeyword}, 184 }, 185 }, 186 { 187 name: "conditional in recipe", 188 text: ` 189 foo.o: foo.c 190 ifndef ENV_VAR 191 $(error ENV_VAR not set) 192 endif 193 @echo "$$ENV_VAR" 194 `, 195 expected: []TokenWithText{ 196 {Text: "ifndef", Role: parser.TokenRoleKeyword}, 197 {Text: "$(error ENV_VAR not set)", Role: makefileTokenRoleVariable}, 198 {Text: "endif", Role: parser.TokenRoleKeyword}, 199 {Text: "@", Role: parser.TokenRoleOperator}, 200 }, 201 }, 202 { 203 name: "one-line recipe with semicolon", 204 text: `all : ; @echo 'hello'`, 205 expected: []TokenWithText{ 206 {Text: "@", Role: parser.TokenRoleOperator}, 207 }, 208 }, 209 { 210 name: "define", 211 text: ` 212 define run-yacc = 213 yacc $(firstword $^) 214 mv y.tab.c $@ 215 endef 216 `, 217 expected: []TokenWithText{ 218 {Text: "define", Role: parser.TokenRoleKeyword}, 219 {Text: "=", Role: parser.TokenRoleOperator}, 220 {Text: "$(firstword $^)", Role: makefileTokenRoleVariable}, 221 {Text: "$@", Role: makefileTokenRoleVariable}, 222 {Text: "endef", Role: parser.TokenRoleKeyword}, 223 }, 224 }, 225 { 226 name: "include", 227 text: `include ../Makefile.defs`, 228 expected: []TokenWithText{ 229 {Text: "include", Role: parser.TokenRoleKeyword}, 230 }, 231 }, 232 { 233 name: "include with error suppression", 234 text: `-include $(DEPS)`, 235 expected: []TokenWithText{ 236 {Text: "-include", Role: parser.TokenRoleKeyword}, 237 {Text: "$(DEPS)", Role: makefileTokenRoleVariable}, 238 }, 239 }, 240 { 241 name: "comment at end of rule", 242 text: ` 243 %.o: %.c # this is a comment 244 $(CC) X=123 245 `, 246 expected: []TokenWithText{ 247 {Text: "%", Role: makefileTokenRolePattern}, 248 {Text: "%", Role: makefileTokenRolePattern}, 249 {Text: "# this is a comment", Role: parser.TokenRoleComment}, 250 {Text: "$(CC)", Role: makefileTokenRoleVariable}, 251 // The "=" should NOT be parsed as an operator, since it's in a recipe command. 252 }, 253 }, 254 { 255 name: "comment at end of rule with variables", 256 text: ` 257 $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) # asdf 258 X=Y 259 foo % bar 260 `, 261 expected: []TokenWithText{ 262 {Text: "$(BUILD_DIR)", Role: makefileTokenRoleVariable}, 263 {Text: "$(TARGET_EXEC)", Role: makefileTokenRoleVariable}, 264 {Text: "$(OBJS)", Role: makefileTokenRoleVariable}, 265 {Text: "# asdf", Role: parser.TokenRoleComment}, 266 // The "=" and "%" should NOT be tokenized, since they're in a recipe command. 267 }, 268 }, 269 } 270 271 for _, tc := range testCases { 272 t.Run(tc.name, func(t *testing.T) { 273 tokens := ParseTokensWithText(MakefileParseFunc(), tc.text) 274 assert.Equal(t, tc.expected, tokens) 275 }) 276 } 277 } 278 279 func FuzzMakefileParseFunc(f *testing.F) { 280 seeds := LoadFuzzTestSeeds(f, "./testdata/makefile/*") 281 FuzzParser(f, MakefileParseFunc(), seeds) 282 }