parser_test.go 14.2 KB
Newer Older
1
package parser
2
3

import (
Michael Yang's avatar
Michael Yang committed
4
	"bytes"
5
	"encoding/binary"
Michael Yang's avatar
Michael Yang committed
6
7
	"fmt"
	"io"
8
9
	"strings"
	"testing"
10
	"unicode/utf16"
11
12

	"github.com/stretchr/testify/assert"
Michael Yang's avatar
lint  
Michael Yang committed
13
	"github.com/stretchr/testify/require"
14
15
	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/unicode"
16
17
)

Michael Yang's avatar
Michael Yang committed
18
func TestParseFileFile(t *testing.T) {
19
20
21
22
23
24
	input := `
FROM model1
ADAPTER adapter1
LICENSE MIT
PARAMETER param1 value1
PARAMETER param2 value2
Josh Yan's avatar
Josh Yan committed
25
26
27
28
29
30
31
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>

{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>

{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>

{{ .Response }}<|eot_id|>"""    
32
33
34
35
`

	reader := strings.NewReader(input)

Michael Yang's avatar
Michael Yang committed
36
	modelfile, err := ParseFile(reader)
Michael Yang's avatar
lint  
Michael Yang committed
37
	require.NoError(t, err)
38
39
40
41
42
43
44

	expectedCommands := []Command{
		{Name: "model", Args: "model1"},
		{Name: "adapter", Args: "adapter1"},
		{Name: "license", Args: "MIT"},
		{Name: "param1", Args: "value1"},
		{Name: "param2", Args: "value2"},
Josh Yan's avatar
Josh Yan committed
45
		{Name: "template", Args: "{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|>"},
46
47
	}

Michael Yang's avatar
Michael Yang committed
48
	assert.Equal(t, expectedCommands, modelfile.Commands)
49
50
}

Josh Yan's avatar
Josh Yan committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
func TestParseFileTrimSpace(t *testing.T) {
	input := `
FROM "     model 1"
ADAPTER      adapter3
LICENSE "MIT       "
PARAMETER param1        value1
PARAMETER param2    value2
TEMPLATE """   {{ if .System }}<|start_header_id|>system<|end_header_id|>

{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>

{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>

{{ .Response }}<|eot_id|>   """    
`

	reader := strings.NewReader(input)

	modelfile, err := ParseFile(reader)
	require.NoError(t, err)

	expectedCommands := []Command{
		{Name: "model", Args: "     model 1"},
		{Name: "adapter", Args: "adapter3"},
		{Name: "license", Args: "MIT       "},
		{Name: "param1", Args: "value1"},
		{Name: "param2", Args: "value2"},
		{Name: "template", Args: "   {{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|>   "},
	}

	assert.Equal(t, expectedCommands, modelfile.Commands)
}

Michael Yang's avatar
Michael Yang committed
84
func TestParseFileFrom(t *testing.T) {
Michael Yang's avatar
tests  
Michael Yang committed
85
86
87
88
89
	var cases = []struct {
		input    string
		expected []Command
		err      error
	}{
Josh Yan's avatar
Josh Yan committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
		{
			"FROM \"FOO  BAR  \"",
			[]Command{{Name: "model", Args: "FOO  BAR  "}},
			nil,
		},
		{
			"FROM \"FOO BAR\"\nPARAMETER param1 value1",
			[]Command{{Name: "model", Args: "FOO BAR"}, {Name: "param1", Args: "value1"}},
			nil,
		},
		{
			"FROM     FOOO BAR    ",
			[]Command{{Name: "model", Args: "FOOO BAR"}},
			nil,
		},
		{
			"FROM /what/is/the path ",
			[]Command{{Name: "model", Args: "/what/is/the path"}},
			nil,
		},
Michael Yang's avatar
tests  
Michael Yang committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
		{
			"FROM foo",
			[]Command{{Name: "model", Args: "foo"}},
			nil,
		},
		{
			"FROM /path/to/model",
			[]Command{{Name: "model", Args: "/path/to/model"}},
			nil,
		},
		{
			"FROM /path/to/model/fp16.bin",
			[]Command{{Name: "model", Args: "/path/to/model/fp16.bin"}},
			nil,
		},
		{
			"FROM llama3:latest",
			[]Command{{Name: "model", Args: "llama3:latest"}},
			nil,
		},
		{
			"FROM llama3:7b-instruct-q4_K_M",
			[]Command{{Name: "model", Args: "llama3:7b-instruct-q4_K_M"}},
			nil,
		},
		{
			"", nil, errMissingFrom,
		},
		{
			"PARAMETER param1 value1",
			nil,
			errMissingFrom,
		},
		{
			"PARAMETER param1 value1\nFROM foo",
			[]Command{{Name: "param1", Args: "value1"}, {Name: "model", Args: "foo"}},
			nil,
		},
Josh Yan's avatar
Josh Yan committed
148
149
		{
			"PARAMETER what the \nFROM lemons make lemonade ",
Josh Yan's avatar
Josh Yan committed
150
			[]Command{{Name: "what", Args: "the"}, {Name: "model", Args: "lemons make lemonade"}},
Josh Yan's avatar
Josh Yan committed
151
152
			nil,
		},
Michael Yang's avatar
tests  
Michael Yang committed
153
	}
154

Michael Yang's avatar
tests  
Michael Yang committed
155
156
	for _, c := range cases {
		t.Run("", func(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
157
			modelfile, err := ParseFile(strings.NewReader(c.input))
Michael Yang's avatar
lint  
Michael Yang committed
158
			require.ErrorIs(t, err, c.err)
Michael Yang's avatar
Michael Yang committed
159
160
161
			if modelfile != nil {
				assert.Equal(t, c.expected, modelfile.Commands)
			}
Michael Yang's avatar
tests  
Michael Yang committed
162
163
		})
	}
164
165
}

Michael Yang's avatar
Michael Yang committed
166
func TestParseFileParametersMissingValue(t *testing.T) {
167
168
169
170
171
172
173
	input := `
FROM foo
PARAMETER param1
`

	reader := strings.NewReader(input)

Michael Yang's avatar
Michael Yang committed
174
	_, err := ParseFile(reader)
Michael Yang's avatar
lint  
Michael Yang committed
175
	require.ErrorIs(t, err, io.ErrUnexpectedEOF)
176
}
177

Michael Yang's avatar
Michael Yang committed
178
func TestParseFileBadCommand(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
179
180
181
182
	input := `
FROM foo
BADCOMMAND param1 value1
`
Michael Yang's avatar
Michael Yang committed
183
	_, err := ParseFile(strings.NewReader(input))
Michael Yang's avatar
lint  
Michael Yang committed
184
	require.ErrorIs(t, err, errInvalidCommand)
Michael Yang's avatar
Michael Yang committed
185
186
}

Michael Yang's avatar
Michael Yang committed
187
func TestParseFileMessages(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
188
189
190
191
192
193
194
195
	var cases = []struct {
		input    string
		expected []Command
		err      error
	}{
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
196
MESSAGE system You are a file parser. Always parse things.
Michael Yang's avatar
Michael Yang committed
197
198
199
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
200
				{Name: "message", Args: "system: You are a file parser. Always parse things."},
Michael Yang's avatar
Michael Yang committed
201
202
203
204
205
			},
			nil,
		},
		{
			`
206
FROM foo
Michael Yang's avatar
Michael Yang committed
207
MESSAGE system You are a file parser. Always parse things.`,
Michael Yang's avatar
Michael Yang committed
208
209
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
210
				{Name: "message", Args: "system: You are a file parser. Always parse things."},
Michael Yang's avatar
Michael Yang committed
211
212
213
214
215
216
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
217
MESSAGE system You are a file parser. Always parse things.
218
219
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
Michael Yang's avatar
Michael Yang committed
220
221
222
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
223
				{Name: "message", Args: "system: You are a file parser. Always parse things."},
Michael Yang's avatar
Michael Yang committed
224
225
226
227
228
229
230
231
232
				{Name: "message", Args: "user: Hey there!"},
				{Name: "message", Args: "assistant: Hello, I want to parse all the things!"},
			},
			nil,
		},
		{
			`
FROM foo
MESSAGE system """
Michael Yang's avatar
Michael Yang committed
233
You are a multiline file parser. Always parse things.
Michael Yang's avatar
Michael Yang committed
234
235
236
237
"""
			`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
238
				{Name: "message", Args: "system: \nYou are a multiline file parser. Always parse things.\n"},
Michael Yang's avatar
Michael Yang committed
239
240
241
242
243
244
245
246
247
			},
			nil,
		},
		{
			`
FROM foo
MESSAGE badguy I'm a bad guy!
`,
			nil,
Michael Yang's avatar
Michael Yang committed
248
			errInvalidMessageRole,
Michael Yang's avatar
Michael Yang committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
		},
		{
			`
FROM foo
MESSAGE system
`,
			nil,
			io.ErrUnexpectedEOF,
		},
		{
			`
FROM foo
MESSAGE system`,
			nil,
			io.ErrUnexpectedEOF,
		},
	}
266

Michael Yang's avatar
Michael Yang committed
267
268
	for _, c := range cases {
		t.Run("", func(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
269
			modelfile, err := ParseFile(strings.NewReader(c.input))
Michael Yang's avatar
lint  
Michael Yang committed
270
			require.ErrorIs(t, err, c.err)
Michael Yang's avatar
Michael Yang committed
271
272
273
			if modelfile != nil {
				assert.Equal(t, c.expected, modelfile.Commands)
			}
Michael Yang's avatar
Michael Yang committed
274
275
276
		})
	}
}
277

Michael Yang's avatar
Michael Yang committed
278
func TestParseFileQuoted(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
279
280
281
282
283
284
285
286
	var cases = []struct {
		multiline string
		expected  []Command
		err       error
	}{
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
287
SYSTEM """
Michael Yang's avatar
Michael Yang committed
288
This is a
Michael Yang's avatar
Michael Yang committed
289
multiline system.
Michael Yang's avatar
Michael Yang committed
290
291
292
293
"""
			`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
294
				{Name: "system", Args: "\nThis is a\nmultiline system.\n"},
Michael Yang's avatar
Michael Yang committed
295
296
297
298
299
300
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
301
SYSTEM """
Michael Yang's avatar
Michael Yang committed
302
This is a
Michael Yang's avatar
Michael Yang committed
303
multiline system."""
Michael Yang's avatar
Michael Yang committed
304
305
306
			`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
307
				{Name: "system", Args: "\nThis is a\nmultiline system."},
Michael Yang's avatar
Michael Yang committed
308
309
310
311
312
313
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
314
315
SYSTEM """This is a
multiline system."""
Michael Yang's avatar
Michael Yang committed
316
317
318
			`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
319
				{Name: "system", Args: "This is a\nmultiline system."},
Michael Yang's avatar
Michael Yang committed
320
321
322
323
324
325
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
326
SYSTEM """This is a multiline system."""
Michael Yang's avatar
Michael Yang committed
327
328
329
			`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
330
				{Name: "system", Args: "This is a multiline system."},
Michael Yang's avatar
Michael Yang committed
331
332
333
334
335
336
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
337
SYSTEM """This is a multiline system.""
Michael Yang's avatar
Michael Yang committed
338
339
340
341
342
343
344
			`,
			nil,
			io.ErrUnexpectedEOF,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
345
SYSTEM "
Michael Yang's avatar
Michael Yang committed
346
347
348
349
350
351
352
			`,
			nil,
			io.ErrUnexpectedEOF,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
353
354
SYSTEM """
This is a multiline system with "quotes".
Michael Yang's avatar
Michael Yang committed
355
356
357
358
"""
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
359
				{Name: "system", Args: "\nThis is a multiline system with \"quotes\".\n"},
Michael Yang's avatar
Michael Yang committed
360
361
362
363
364
365
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
366
SYSTEM """"""
Michael Yang's avatar
Michael Yang committed
367
368
369
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
370
				{Name: "system", Args: ""},
Michael Yang's avatar
Michael Yang committed
371
372
373
374
375
376
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
377
SYSTEM ""
Michael Yang's avatar
Michael Yang committed
378
379
380
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
381
				{Name: "system", Args: ""},
Michael Yang's avatar
Michael Yang committed
382
383
384
385
386
387
			},
			nil,
		},
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
388
SYSTEM "'"
Michael Yang's avatar
Michael Yang committed
389
390
391
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
392
				{Name: "system", Args: "'"},
Michael Yang's avatar
Michael Yang committed
393
394
395
			},
			nil,
		},
Michael Yang's avatar
tests  
Michael Yang committed
396
397
398
		{
			`
FROM foo
Michael Yang's avatar
Michael Yang committed
399
SYSTEM """''"'""'""'"'''''""'""'"""
Michael Yang's avatar
tests  
Michael Yang committed
400
401
402
`,
			[]Command{
				{Name: "model", Args: "foo"},
Michael Yang's avatar
Michael Yang committed
403
404
405
406
407
408
409
410
411
412
413
414
415
				{Name: "system", Args: `''"'""'""'"'''''""'""'`},
			},
			nil,
		},
		{
			`
FROM foo
TEMPLATE """
{{ .Prompt }}
"""`,
			[]Command{
				{Name: "model", Args: "foo"},
				{Name: "template", Args: "\n{{ .Prompt }}\n"},
Michael Yang's avatar
tests  
Michael Yang committed
416
417
418
			},
			nil,
		},
419
420
	}

Michael Yang's avatar
Michael Yang committed
421
422
	for _, c := range cases {
		t.Run("", func(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
423
			modelfile, err := ParseFile(strings.NewReader(c.multiline))
Michael Yang's avatar
lint  
Michael Yang committed
424
			require.ErrorIs(t, err, c.err)
Michael Yang's avatar
Michael Yang committed
425
426
427
			if modelfile != nil {
				assert.Equal(t, c.expected, modelfile.Commands)
			}
Michael Yang's avatar
Michael Yang committed
428
429
		})
	}
430
431
}

Michael Yang's avatar
Michael Yang committed
432
func TestParseFileParameters(t *testing.T) {
Michael Yang's avatar
tests  
Michael Yang committed
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
	var cases = map[string]struct {
		name, value string
	}{
		"numa true":                    {"numa", "true"},
		"num_ctx 1":                    {"num_ctx", "1"},
		"num_batch 1":                  {"num_batch", "1"},
		"num_gqa 1":                    {"num_gqa", "1"},
		"num_gpu 1":                    {"num_gpu", "1"},
		"main_gpu 1":                   {"main_gpu", "1"},
		"low_vram true":                {"low_vram", "true"},
		"f16_kv true":                  {"f16_kv", "true"},
		"logits_all true":              {"logits_all", "true"},
		"vocab_only true":              {"vocab_only", "true"},
		"use_mmap true":                {"use_mmap", "true"},
		"use_mlock true":               {"use_mlock", "true"},
		"num_thread 1":                 {"num_thread", "1"},
		"num_keep 1":                   {"num_keep", "1"},
		"seed 1":                       {"seed", "1"},
		"num_predict 1":                {"num_predict", "1"},
		"top_k 1":                      {"top_k", "1"},
		"top_p 1.0":                    {"top_p", "1.0"},
		"tfs_z 1.0":                    {"tfs_z", "1.0"},
		"typical_p 1.0":                {"typical_p", "1.0"},
		"repeat_last_n 1":              {"repeat_last_n", "1"},
		"temperature 1.0":              {"temperature", "1.0"},
		"repeat_penalty 1.0":           {"repeat_penalty", "1.0"},
		"presence_penalty 1.0":         {"presence_penalty", "1.0"},
		"frequency_penalty 1.0":        {"frequency_penalty", "1.0"},
		"mirostat 1":                   {"mirostat", "1"},
		"mirostat_tau 1.0":             {"mirostat_tau", "1.0"},
		"mirostat_eta 1.0":             {"mirostat_eta", "1.0"},
		"penalize_newline true":        {"penalize_newline", "true"},
		"stop ### User:":               {"stop", "### User:"},
Josh Yan's avatar
Josh Yan committed
466
		"stop ### User: ":              {"stop", "### User:"},
Michael Yang's avatar
tests  
Michael Yang committed
467
468
469
470
471
472
473
		"stop \"### User:\"":           {"stop", "### User:"},
		"stop \"### User: \"":          {"stop", "### User: "},
		"stop \"\"\"### User:\"\"\"":   {"stop", "### User:"},
		"stop \"\"\"### User:\n\"\"\"": {"stop", "### User:\n"},
		"stop <|endoftext|>":           {"stop", "<|endoftext|>"},
		"stop <|eot_id|>":              {"stop", "<|eot_id|>"},
		"stop </s>":                    {"stop", "</s>"},
Michael Yang's avatar
Michael Yang committed
474
	}
475

Michael Yang's avatar
tests  
Michael Yang committed
476
477
	for k, v := range cases {
		t.Run(k, func(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
478
479
			var b bytes.Buffer
			fmt.Fprintln(&b, "FROM foo")
Michael Yang's avatar
tests  
Michael Yang committed
480
			fmt.Fprintln(&b, "PARAMETER", k)
Michael Yang's avatar
Michael Yang committed
481
			modelfile, err := ParseFile(&b)
Michael Yang's avatar
lint  
Michael Yang committed
482
			require.NoError(t, err)
Michael Yang's avatar
tests  
Michael Yang committed
483
484
485
486

			assert.Equal(t, []Command{
				{Name: "model", Args: "foo"},
				{Name: v.name, Args: v.value},
Michael Yang's avatar
Michael Yang committed
487
			}, modelfile.Commands)
Michael Yang's avatar
Michael Yang committed
488
489
490
491
		})
	}
}

Michael Yang's avatar
Michael Yang committed
492
func TestParseFileComments(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
493
494
495
496
497
498
499
	var cases = []struct {
		input    string
		expected []Command
	}{
		{
			`
# comment
500
FROM foo
Michael Yang's avatar
Michael Yang committed
501
502
503
504
505
506
	`,
			[]Command{
				{Name: "model", Args: "foo"},
			},
		},
	}
507

Michael Yang's avatar
Michael Yang committed
508
509
	for _, c := range cases {
		t.Run("", func(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
510
			modelfile, err := ParseFile(strings.NewReader(c.input))
Michael Yang's avatar
lint  
Michael Yang committed
511
			require.NoError(t, err)
Michael Yang's avatar
Michael Yang committed
512
			assert.Equal(t, c.expected, modelfile.Commands)
Michael Yang's avatar
Michael Yang committed
513
514
		})
	}
515
}
Michael Yang's avatar
Michael Yang committed
516

Michael Yang's avatar
Michael Yang committed
517
func TestParseFileFormatParseFile(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
518
519
520
521
522
523
524
525
	var cases = []string{
		`
FROM foo
ADAPTER adapter1
LICENSE MIT
PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE template1
Michael Yang's avatar
Michael Yang committed
526
MESSAGE system You are a file parser. Always parse things.
Michael Yang's avatar
Michael Yang committed
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
`,
		`
FROM foo
ADAPTER adapter1
LICENSE MIT
PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE template1
MESSAGE system """
You are a store greeter. Always responsed with "Hello!".
"""
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
`,
		`
FROM foo
ADAPTER adapter1
LICENSE """
Very long and boring legal text.
Blah blah blah.
"Oh look, a quote!"
"""

PARAMETER param1 value1
PARAMETER param2 value2
TEMPLATE template1
MESSAGE system """
You are a store greeter. Always responsed with "Hello!".
"""
MESSAGE user Hey there!
MESSAGE assistant Hello, I want to parse all the things!
560
561
562
563
`,
		`
FROM foo
SYSTEM ""
Michael Yang's avatar
Michael Yang committed
564
565
566
567
568
`,
	}

	for _, c := range cases {
		t.Run("", func(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
569
			modelfile, err := ParseFile(strings.NewReader(c))
Michael Yang's avatar
lint  
Michael Yang committed
570
			require.NoError(t, err)
Michael Yang's avatar
Michael Yang committed
571

Michael Yang's avatar
Michael Yang committed
572
			modelfile2, err := ParseFile(strings.NewReader(modelfile.String()))
Michael Yang's avatar
lint  
Michael Yang committed
573
			require.NoError(t, err)
Michael Yang's avatar
Michael Yang committed
574

Michael Yang's avatar
Michael Yang committed
575
			assert.Equal(t, modelfile, modelfile2)
Michael Yang's avatar
Michael Yang committed
576
577
578
		})
	}
}
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593

func TestParseFileUTF16ParseFile(t *testing.T) {
	data := `FROM bob
PARAMETER param1 1
PARAMETER param2 4096
SYSTEM You are a utf16 file.
`

	expected := []Command{
		{Name: "model", Args: "bob"},
		{Name: "param1", Args: "1"},
		{Name: "param2", Args: "4096"},
		{Name: "system", Args: "You are a utf16 file."},
	}

594
595
596
597
	t.Run("le", func(t *testing.T) {
		var b bytes.Buffer
		require.NoError(t, binary.Write(&b, binary.LittleEndian, []byte{0xff, 0xfe}))
		require.NoError(t, binary.Write(&b, binary.LittleEndian, utf16.Encode([]rune(data))))
598

599
600
		actual, err := ParseFile(&b)
		require.NoError(t, err)
601

602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
		assert.Equal(t, expected, actual.Commands)
	})

	t.Run("be", func(t *testing.T) {
		var b bytes.Buffer
		require.NoError(t, binary.Write(&b, binary.BigEndian, []byte{0xfe, 0xff}))
		require.NoError(t, binary.Write(&b, binary.BigEndian, utf16.Encode([]rune(data))))

		actual, err := ParseFile(&b)
		require.NoError(t, err)
		assert.Equal(t, expected, actual.Commands)
	})
}

func TestParseMultiByte(t *testing.T) {
	input := `FROM test
	SYSTEM 你好👋`

	expect := []Command{
		{Name: "model", Args: "test"},
		{Name: "system", Args: "你好👋"},
	}

	encodings := []encoding.Encoding{
		unicode.UTF8,
		unicode.UTF16(unicode.LittleEndian, unicode.UseBOM),
		unicode.UTF16(unicode.BigEndian, unicode.UseBOM),
	}

	for _, encoding := range encodings {
		t.Run(fmt.Sprintf("%s", encoding), func(t *testing.T) {
			s, err := encoding.NewEncoder().String(input)
			require.NoError(t, err)

			actual, err := ParseFile(strings.NewReader(s))
			require.NoError(t, err)

			assert.Equal(t, expect, actual.Commands)
		})
	}
642
}