1package stdlib
2
3import (
4	"fmt"
5	"testing"
6
7	"github.com/zclconf/go-cty/cty"
8)
9
10func TestFormat(t *testing.T) {
11	tests := []struct {
12		Format  cty.Value
13		Args    []cty.Value
14		Want    cty.Value
15		WantErr string
16	}{
17		{
18			cty.StringVal(""),
19			nil,
20			cty.StringVal(""),
21			``,
22		},
23		{
24			cty.StringVal("hello"),
25			nil,
26			cty.StringVal("hello"),
27			``,
28		},
29		{
30			cty.StringVal("100%% successful"),
31			nil,
32			cty.StringVal("100% successful"),
33			``,
34		},
35		{
36			cty.StringVal("100%%"),
37			nil,
38			cty.StringVal("100%"),
39			``,
40		},
41
42		// Default formats
43		{
44			cty.StringVal("string %v"),
45			[]cty.Value{cty.StringVal("hello")},
46			cty.StringVal("string hello"),
47			``,
48		},
49		{
50			cty.StringVal("string %[2]v"),
51			[]cty.Value{cty.True, cty.StringVal("hello")},
52			cty.StringVal("string hello"),
53			``,
54		},
55		{
56			cty.StringVal("string %#v"),
57			[]cty.Value{cty.StringVal("hello")},
58			cty.StringVal(`string "hello"`),
59			``,
60		},
61		{
62			cty.StringVal("number %v"),
63			[]cty.Value{cty.NumberIntVal(2)},
64			cty.StringVal("number 2"),
65			``,
66		},
67		{
68			cty.StringVal("number %#v"),
69			[]cty.Value{cty.NumberIntVal(2)},
70			cty.StringVal("number 2"),
71			``,
72		},
73		{
74			cty.StringVal("bool %v"),
75			[]cty.Value{cty.True},
76			cty.StringVal("bool true"),
77			``,
78		},
79		{
80			cty.StringVal("bool %#v"),
81			[]cty.Value{cty.True},
82			cty.StringVal("bool true"),
83			``,
84		},
85		{
86			cty.StringVal("object %v"),
87			[]cty.Value{cty.EmptyObjectVal},
88			cty.StringVal("object {}"),
89			``,
90		},
91		{
92			cty.StringVal("tuple %v"),
93			[]cty.Value{cty.EmptyTupleVal},
94			cty.StringVal("tuple []"),
95			``,
96		},
97		{
98			cty.StringVal("tuple with unknown %v"),
99			[]cty.Value{cty.TupleVal([]cty.Value{
100				cty.UnknownVal(cty.String),
101			})},
102			cty.UnknownVal(cty.String),
103			``,
104		},
105		{
106			cty.StringVal("%%%v"),
107			[]cty.Value{cty.False},
108			cty.StringVal("%false"),
109			``,
110		},
111		{
112			cty.StringVal("%v"),
113			[]cty.Value{cty.NullVal(cty.Bool)},
114			cty.StringVal("null"),
115			``,
116		},
117
118		// Strings
119		{
120			cty.StringVal("Hello, %s!"),
121			[]cty.Value{cty.StringVal("Ermintrude")},
122			cty.StringVal("Hello, Ermintrude!"),
123			``,
124		},
125		{
126			cty.StringVal("Hello, %[2]s!"),
127			[]cty.Value{cty.StringVal("Stephen"), cty.StringVal("Ermintrude")},
128			cty.StringVal("Hello, Ermintrude!"),
129			``,
130		},
131		{
132			cty.StringVal("Hello, %q... if that _is_ your real name!"),
133			[]cty.Value{cty.StringVal("Ermintrude")},
134			cty.StringVal(`Hello, "Ermintrude"... if that _is_ your real name!`),
135			``,
136		},
137		{
138			cty.StringVal("This statement is %s"),
139			[]cty.Value{cty.False},
140			cty.StringVal("This statement is false"),
141			``,
142		},
143		{
144			cty.StringVal("This statement is %q"),
145			[]cty.Value{cty.False},
146			cty.StringVal(`This statement is "false"`),
147			``,
148		},
149		{
150			cty.StringVal("%s"),
151			[]cty.Value{cty.NullVal(cty.String)},
152			cty.NilVal,
153			`unsupported value for "%s" at 0: null value cannot be formatted`,
154		},
155		{
156			cty.StringVal("%10s"),
157			[]cty.Value{cty.StringVal("hello")},
158			cty.StringVal(`     hello`),
159			``,
160		},
161		{
162			cty.StringVal("%-10s"),
163			[]cty.Value{cty.StringVal("hello")},
164			cty.StringVal(`hello     `),
165			``,
166		},
167		{
168			cty.StringVal("%4s"),
169			[]cty.Value{cty.StringVal("����")},
170			cty.StringVal(`   ����`), // three spaces because this emoji sequence is a single grapheme cluster
171			``,
172		},
173		{
174			cty.StringVal("%-4s"),
175			[]cty.Value{cty.StringVal("����")},
176			cty.StringVal(`����   `), // three spaces because this emoji sequence is a single grapheme cluster
177			``,
178		},
179		{
180			cty.StringVal("%q"),
181			[]cty.Value{cty.StringVal("����")},
182			cty.StringVal(`"����"`),
183			``,
184		},
185		{
186			cty.StringVal("%6q"),
187			[]cty.Value{cty.StringVal("����")},
188			cty.StringVal(`   "����"`), // three spaces because this emoji sequence is a single grapheme cluster
189			``,
190		},
191		{
192			cty.StringVal("%-6q"),
193			[]cty.Value{cty.StringVal("����")},
194			cty.StringVal(`"����"   `), // three spaces because this emoji sequence is a single grapheme cluster
195			``,
196		},
197		{
198			cty.StringVal("%.2s"),
199			[]cty.Value{cty.StringVal("hello")},
200			cty.StringVal(`he`),
201			``,
202		},
203		{
204			cty.StringVal("%.2q"),
205			[]cty.Value{cty.StringVal("hello")},
206			cty.StringVal(`"he"`),
207			``,
208		},
209		{
210			cty.StringVal("%.5s"),
211			[]cty.Value{cty.StringVal("日本語日本語")},
212			cty.StringVal(`日本語日本`),
213			``,
214		},
215		{
216			cty.StringVal("%.1q"),
217			[]cty.Value{cty.StringVal("日本語日本語")},
218			cty.StringVal(`"日"`),
219			``,
220		},
221		{
222			cty.StringVal("%.10s"),
223			[]cty.Value{cty.StringVal("hello")},
224			cty.StringVal(`hello`),
225			``,
226		},
227		{
228			cty.StringVal("%4.2s"),
229			[]cty.Value{cty.StringVal("hello")},
230			cty.StringVal(`  he`),
231			``,
232		},
233		{
234			cty.StringVal("%6.2q"),
235			[]cty.Value{cty.StringVal("hello")},
236			cty.StringVal(`  "he"`),
237			``,
238		},
239		{
240			cty.StringVal("%-4.2s"),
241			[]cty.Value{cty.StringVal("hello")},
242			cty.StringVal(`he  `),
243			``,
244		},
245		{
246			cty.StringVal("%q"),
247			[]cty.Value{cty.StringVal("Hello\nWorld")},
248			cty.StringVal(`"Hello\nWorld"`),
249			``,
250		},
251
252		// Booleans
253		{
254			cty.StringVal("This statement is %t"),
255			[]cty.Value{cty.False},
256			cty.StringVal("This statement is false"),
257			``,
258		},
259		{
260			cty.StringVal("This statement is %[2]t"),
261			[]cty.Value{cty.True, cty.False},
262			cty.StringVal("This statement is false"),
263			``,
264		},
265		{
266			cty.StringVal("This statement is %t"),
267			[]cty.Value{cty.True},
268			cty.StringVal("This statement is true"),
269			``,
270		},
271		{
272			cty.StringVal("This statement is %t"),
273			[]cty.Value{cty.StringVal("false")},
274			cty.StringVal("This statement is false"),
275			``,
276		},
277
278		// Integer Numbers
279		{
280			cty.StringVal("%d green bottles standing on the wall"),
281			[]cty.Value{cty.NumberIntVal(10)},
282			cty.StringVal("10 green bottles standing on the wall"),
283			``,
284		},
285		{
286			cty.StringVal("%[2]d things"),
287			[]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(10)},
288			cty.StringVal("10 things"),
289			``,
290		},
291		{
292			cty.StringVal("%+d green bottles standing on the wall"),
293			[]cty.Value{cty.NumberIntVal(10)},
294			cty.StringVal("+10 green bottles standing on the wall"),
295			``,
296		},
297		{
298			cty.StringVal("% d green bottles standing on the wall"),
299			[]cty.Value{cty.NumberIntVal(10)},
300			cty.StringVal(" 10 green bottles standing on the wall"),
301			``,
302		},
303		{
304			cty.StringVal("%5d green bottles standing on the wall"),
305			[]cty.Value{cty.NumberIntVal(10)},
306			cty.StringVal("   10 green bottles standing on the wall"),
307			``,
308		},
309		{
310			cty.StringVal("%-5d green bottles standing on the wall"),
311			[]cty.Value{cty.NumberIntVal(10)},
312			cty.StringVal("10    green bottles standing on the wall"),
313			``,
314		},
315		{
316			cty.StringVal("%d green bottles standing on the wall"),
317			[]cty.Value{cty.True},
318			cty.NilVal,
319			`unsupported value for "%d" at 0: number required`,
320		},
321		{
322			cty.StringVal("%b"),
323			[]cty.Value{cty.NumberIntVal(5)},
324			cty.StringVal("101"),
325			``,
326		},
327		{
328			cty.StringVal("%o"),
329			[]cty.Value{cty.NumberIntVal(9)},
330			cty.StringVal("11"),
331			``,
332		},
333		{
334			cty.StringVal("%x"),
335			[]cty.Value{cty.NumberIntVal(254)},
336			cty.StringVal("fe"),
337			``,
338		},
339		{
340			cty.StringVal("%X"),
341			[]cty.Value{cty.NumberIntVal(254)},
342			cty.StringVal("FE"),
343			``,
344		},
345
346		// Floating-point numbers
347		{
348			cty.StringVal("%f things"),
349			[]cty.Value{cty.NumberIntVal(10)},
350			cty.StringVal("10.000000 things"),
351			``,
352		},
353		{
354			cty.StringVal("%[2]f things"),
355			[]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(10)},
356			cty.StringVal("10.000000 things"),
357			``,
358		},
359		{
360			cty.StringVal("%+f things"),
361			[]cty.Value{cty.NumberIntVal(10)},
362			cty.StringVal("+10.000000 things"),
363			``,
364		},
365		{
366			cty.StringVal("% f things"),
367			[]cty.Value{cty.NumberIntVal(10)},
368			cty.StringVal(" 10.000000 things"),
369			``,
370		},
371		{
372			cty.StringVal("%+f things"),
373			[]cty.Value{cty.NumberIntVal(-10)},
374			cty.StringVal("-10.000000 things"),
375			``,
376		},
377		{
378			cty.StringVal("% f things"),
379			[]cty.Value{cty.NumberIntVal(-10)},
380			cty.StringVal("-10.000000 things"),
381			``,
382		},
383		{
384			cty.StringVal("%f things"),
385			[]cty.Value{cty.StringVal("100000000000000000000000000000000000001")},
386			cty.StringVal("100000000000000000000000000000000000001.000000 things"),
387			``,
388		},
389		{
390			cty.StringVal("%f things"),
391			[]cty.Value{cty.StringVal("1.00000000000000000000000000000000000001")},
392			cty.StringVal("1.000000 things"),
393			``,
394		},
395		{
396			cty.StringVal("%.4f things"),
397			[]cty.Value{cty.StringVal("1.00000000000000000000000000000000000001")},
398			cty.StringVal("1.0000 things"),
399			``,
400		},
401		{
402			cty.StringVal("%.1f things"),
403			[]cty.Value{cty.StringVal("1.06")},
404			cty.StringVal("1.1 things"),
405			``,
406		},
407		{
408			cty.StringVal("%e things"),
409			[]cty.Value{cty.NumberIntVal(1000)},
410			cty.StringVal("1.000000e+03 things"),
411			``,
412		},
413		{
414			cty.StringVal("%E things"),
415			[]cty.Value{cty.NumberIntVal(1000)},
416			cty.StringVal("1.000000E+03 things"),
417			``,
418		},
419		{
420			cty.StringVal("%g things"),
421			[]cty.Value{cty.NumberIntVal(1000)},
422			cty.StringVal("1000 things"),
423			``,
424		},
425		{
426			cty.StringVal("%G things"),
427			[]cty.Value{cty.NumberIntVal(1000)},
428			cty.StringVal("1000 things"),
429			``,
430		},
431		{
432			cty.StringVal("%g things"),
433			[]cty.Value{cty.StringVal("0.00000000000000000000001")},
434			cty.StringVal("1e-23 things"),
435			``,
436		},
437		{
438			cty.StringVal("%G things"),
439			[]cty.Value{cty.StringVal("0.00000000000000000000001")},
440			cty.StringVal("1E-23 things"),
441			``,
442		},
443
444		// Unknowns
445		{
446			cty.UnknownVal(cty.String),
447			[]cty.Value{cty.True},
448			cty.UnknownVal(cty.String),
449			``,
450		},
451		{
452			cty.UnknownVal(cty.Bool),
453			[]cty.Value{cty.True},
454			cty.NilVal,
455			`string required, but received bool`,
456		},
457		{
458			cty.StringVal("Hello, %s!"),
459			[]cty.Value{cty.UnknownVal(cty.String)},
460			cty.UnknownVal(cty.String),
461			``,
462		},
463		{
464			cty.StringVal("Hello, %[2]s!"),
465			[]cty.Value{cty.UnknownVal(cty.String), cty.StringVal("Ermintrude")},
466			cty.UnknownVal(cty.String),
467			``,
468		},
469
470		// Invalids
471		{
472			cty.StringVal("%s is not in the args list"),
473			nil,
474			cty.NilVal,
475			`not enough arguments for "%s" at 0: need index 1 but have 0 total`,
476		},
477		{
478			cty.StringVal("%[3]s is not in the args list"),
479			[]cty.Value{cty.True, cty.True},
480			cty.NilVal,
481			`not enough arguments for "%[3]s" at 0: need index 3 but have 2 total`,
482		},
483		{
484			cty.StringVal("%[0]s is not valid because args are 1-based"),
485			[]cty.Value{cty.True, cty.True},
486			cty.NilVal,
487			`unrecognized format character '0' at offset 2`,
488		},
489		{
490			cty.StringVal("%v %v %v"),
491			[]cty.Value{cty.True, cty.True},
492			cty.NilVal,
493			`not enough arguments for "%v" at 6: need index 3 but have 2 total`,
494		},
495		{
496			cty.StringVal("%z is not a valid sequence"),
497			[]cty.Value{cty.NumberIntVal(10)},
498			cty.NilVal,
499			`unsupported format verb 'z' in "%z" at offset 0`,
500		},
501		{
502			cty.StringVal("%#z is not a valid sequence"),
503			[]cty.Value{cty.NumberIntVal(10)},
504			cty.NilVal,
505			`unsupported format verb 'z' in "%#z" at offset 0`,
506		},
507		{
508			cty.StringVal("%012z is not a valid sequence"),
509			[]cty.Value{cty.NumberIntVal(10)},
510			cty.NilVal,
511			`unsupported format verb 'z' in "%012z" at offset 0`,
512		},
513		{
514			cty.StringVal("%☠ is not a valid sequence"),
515			[]cty.Value{cty.NumberIntVal(10)},
516			cty.NilVal,
517			`unrecognized format character '☠' at offset 1`,
518		},
519		{
520			cty.StringVal("%���� is not a valid sequence"),
521			[]cty.Value{cty.NumberIntVal(10)},
522			cty.NilVal,
523			`unrecognized format character '��' at offset 1`, // since this is a grammar-level error, we don't get the full grapheme cluster
524		},
525		{
526			cty.NullVal(cty.String),
527			[]cty.Value{cty.NumberIntVal(10)},
528			cty.NilVal,
529			`argument must not be null`,
530		},
531		{
532			cty.StringVal("no format verbs at all"),
533			[]cty.Value{cty.NumberIntVal(10)},
534			cty.NilVal,
535			`too many arguments; no verbs in format string`,
536		},
537		{
538			cty.StringVal("only one verb %d"),
539			[]cty.Value{cty.NumberIntVal(10), cty.NumberIntVal(11)},
540			cty.NilVal,
541			`too many arguments; only 1 used by format string`,
542		},
543
544		{
545			cty.StringVal("hello %s").Mark(1),
546			[]cty.Value{cty.StringVal("world")},
547			cty.StringVal("hello world").Mark(1),
548			``,
549		},
550		{
551			cty.StringVal("hello %s"),
552			[]cty.Value{cty.StringVal("world").Mark(1)},
553			cty.StringVal("hello world").Mark(1),
554			``,
555		},
556		{
557			cty.StringVal("hello %s").Mark(0),
558			[]cty.Value{cty.StringVal("world").Mark(1)},
559			cty.StringVal("hello world").WithMarks(cty.NewValueMarks(0, 1)),
560			``,
561		},
562	}
563
564	for i, test := range tests {
565		t.Run(fmt.Sprintf("%02d-%#v", i, test.Format), func(t *testing.T) {
566			got, err := Format(test.Format, test.Args...)
567
568			if test.WantErr == "" {
569				if err != nil {
570					t.Fatalf("unexpected error: %s", err)
571				}
572			} else {
573				if err == nil {
574					t.Fatalf("no error; want %q", test.WantErr)
575				}
576				errStr := err.Error()
577				if errStr != test.WantErr {
578					t.Fatalf("wrong error\ngot:  %s\nwant: %s", errStr, test.WantErr)
579				}
580				return
581			}
582
583			if test.Want != cty.NilVal {
584				if !got.RawEquals(test.Want) {
585					t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
586				}
587			} else {
588				t.Errorf("unexpected success %#v; want error", got)
589			}
590		})
591	}
592}
593func TestFormatList(t *testing.T) {
594	tests := []struct {
595		Format  cty.Value
596		Args    []cty.Value
597		Want    cty.Value
598		WantErr string
599	}{
600		0: {
601			cty.StringVal(""),
602			nil,
603			cty.ListVal([]cty.Value{
604				cty.StringVal(""),
605			}),
606			``,
607		},
608		1: {
609			cty.StringVal("hello"),
610			nil,
611			cty.ListVal([]cty.Value{
612				cty.StringVal("hello"),
613			}),
614			``,
615		},
616		2: {
617			cty.StringVal("100%% successful"),
618			nil,
619			cty.ListVal([]cty.Value{
620				cty.StringVal("100% successful"),
621			}),
622			``,
623		},
624		3: {
625			cty.StringVal("100%%"),
626			nil,
627			cty.ListVal([]cty.Value{
628				cty.StringVal("100%"),
629			}),
630			``,
631		},
632
633		4: {
634			cty.StringVal("%s"),
635			[]cty.Value{cty.StringVal("hello")},
636			cty.ListVal([]cty.Value{
637				cty.StringVal("hello"),
638			}),
639			``,
640		},
641		5: {
642			cty.StringVal("%s"),
643			[]cty.Value{
644				cty.ListVal([]cty.Value{
645					cty.StringVal("hello"),
646				}),
647			},
648			cty.ListVal([]cty.Value{
649				cty.StringVal("hello"),
650			}),
651			``,
652		},
653		6: {
654			cty.StringVal("%s"),
655			[]cty.Value{
656				cty.ListVal([]cty.Value{
657					cty.StringVal("hello"),
658					cty.StringVal("world"),
659				}),
660			},
661			cty.ListVal([]cty.Value{
662				cty.StringVal("hello"),
663				cty.StringVal("world"),
664			}),
665			``,
666		},
667		7: {
668			cty.StringVal("%s %s"),
669			[]cty.Value{
670				cty.ListVal([]cty.Value{
671					cty.StringVal("hello"),
672					cty.StringVal("goodbye"),
673				}),
674				cty.ListVal([]cty.Value{
675					cty.StringVal("world"),
676					cty.StringVal("universe"),
677				}),
678			},
679			cty.ListVal([]cty.Value{
680				cty.StringVal("hello world"),
681				cty.StringVal("goodbye universe"),
682			}),
683			``,
684		},
685		8: {
686			cty.StringVal("%s %s"),
687			[]cty.Value{
688				cty.ListVal([]cty.Value{
689					cty.StringVal("hello"),
690					cty.StringVal("goodbye"),
691				}),
692				cty.StringVal("world"),
693			},
694			cty.ListVal([]cty.Value{
695				cty.StringVal("hello world"),
696				cty.StringVal("goodbye world"),
697			}),
698			``,
699		},
700		9: {
701			cty.StringVal("%s %s"),
702			[]cty.Value{
703				cty.StringVal("hello"),
704				cty.ListVal([]cty.Value{
705					cty.StringVal("world"),
706					cty.StringVal("universe"),
707				}),
708			},
709			cty.ListVal([]cty.Value{
710				cty.StringVal("hello world"),
711				cty.StringVal("hello universe"),
712			}),
713			``,
714		},
715		10: {
716			cty.StringVal("%s %s"),
717			[]cty.Value{
718				cty.ListVal([]cty.Value{
719					cty.StringVal("hello"),
720					cty.StringVal("goodbye"),
721				}),
722				cty.ListVal([]cty.Value{
723					cty.StringVal("world"),
724				}),
725			},
726			cty.ListValEmpty(cty.String),
727			`argument 2 has length 1, which is inconsistent with argument 1 of length 2`,
728		},
729		11: {
730			cty.StringVal("%s"),
731			[]cty.Value{cty.EmptyObjectVal},
732			cty.ListValEmpty(cty.String),
733			`error on format iteration 0: unsupported value for "%s" at 0: string required`,
734		},
735		12: {
736			cty.StringVal("%v"),
737			[]cty.Value{cty.EmptyTupleVal},
738			cty.ListValEmpty(cty.String), // no items because our given tuple is empty
739			``,
740		},
741		13: {
742			cty.StringVal("%v"),
743			[]cty.Value{cty.NullVal(cty.List(cty.String))},
744			cty.ListVal([]cty.Value{
745				cty.StringVal("null"), // we treat a null list like a list whose elements are all null
746			}),
747			``,
748		},
749
750		14: {
751			cty.UnknownVal(cty.String),
752			[]cty.Value{
753				cty.True,
754			},
755			cty.UnknownVal(cty.List(cty.String)),
756			``,
757		},
758		15: {
759			cty.StringVal("%v"),
760			[]cty.Value{
761				cty.UnknownVal(cty.String),
762			},
763			cty.ListVal([]cty.Value{
764				cty.UnknownVal(cty.String),
765			}),
766			``,
767		},
768		16: {
769			cty.StringVal("%v"),
770			[]cty.Value{
771				cty.NullVal(cty.String),
772			},
773			cty.ListVal([]cty.Value{
774				cty.StringVal("null"),
775			}),
776			``,
777		},
778		17: {
779			cty.StringVal("%v"),
780			[]cty.Value{
781				cty.UnknownVal(cty.List(cty.String)),
782			},
783			cty.UnknownVal(cty.List(cty.String)),
784			``,
785		},
786		18: {
787			cty.StringVal("%v"),
788			[]cty.Value{
789				cty.ListVal([]cty.Value{
790					cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
791					cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String)}),
792					cty.TupleVal([]cty.Value{cty.StringVal("world")}),
793				}),
794			},
795			cty.ListVal([]cty.Value{
796				cty.StringVal(`["hello"]`),
797				cty.UnknownVal(cty.String),
798				cty.StringVal(`["world"]`),
799			}),
800			``,
801		},
802		19: {
803			cty.StringVal("%v"),
804			[]cty.Value{
805				cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})),
806			},
807			cty.UnknownVal(cty.List(cty.String)),
808			``,
809		},
810		20: {
811			cty.StringVal("%s %s"),
812			[]cty.Value{
813				cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})),
814				cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
815			},
816			cty.UnknownVal(cty.List(cty.String)),
817			`argument 2 has length 2, which is inconsistent with argument 1 of length 1`,
818		},
819		21: {
820			cty.StringVal("%s %s"),
821			[]cty.Value{
822				cty.ListVal([]cty.Value{cty.StringVal("hi")}),
823				cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
824			},
825			cty.UnknownVal(cty.List(cty.String)),
826			`argument 2 has length 2, which is inconsistent with argument 1 of length 1`,
827		},
828		22: {
829			cty.StringVal("%v"),
830			[]cty.Value{
831				cty.SetVal([]cty.Value{
832					cty.StringVal("hello"),
833					cty.UnknownVal(cty.String),
834				}),
835			},
836			cty.UnknownVal(cty.List(cty.String)),
837			``,
838		},
839	}
840
841	for i, test := range tests {
842		t.Run(fmt.Sprintf("%02d-%#v", i, test.Format), func(t *testing.T) {
843			got, err := FormatList(test.Format, test.Args...)
844
845			if test.WantErr == "" {
846				if err != nil {
847					t.Fatalf("unexpected error: %s", err)
848				}
849			} else {
850				if err == nil {
851					t.Fatalf("no error; want %q", test.WantErr)
852				}
853				errStr := err.Error()
854				if errStr != test.WantErr {
855					t.Fatalf("wrong error\ngot:  %s\nwant: %s", errStr, test.WantErr)
856				}
857				return
858			}
859
860			if test.Want != cty.NilVal {
861				if !got.RawEquals(test.Want) {
862					t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
863				}
864			} else {
865				t.Errorf("unexpected success %#v; want error", got)
866			}
867		})
868	}
869}
870