1
2
3
4
5
6
7
8
9
10 package loader_test
11
12 import (
13 "fmt"
14 "go/build"
15 "go/constant"
16 "go/types"
17 "os"
18 "path/filepath"
19 "reflect"
20 "runtime"
21 "sort"
22 "strings"
23 "sync"
24 "testing"
25
26 "golang.org/x/tools/go/buildutil"
27 "golang.org/x/tools/go/loader"
28 "golang.org/x/tools/internal/testenv"
29 )
30
31 func TestMain(m *testing.M) {
32 testenv.ExitIfSmallMachine()
33 os.Exit(m.Run())
34 }
35
36
37
38 func TestFromArgs(t *testing.T) {
39 type result struct {
40 Err string
41 Rest []string
42 ImportPkgs map[string]bool
43 CreatePkgs []loader.PkgSpec
44 }
45 for _, test := range []struct {
46 args []string
47 tests bool
48 want result
49 }{
50
51 {
52 args: []string{"nosuchpkg", "errors"},
53 want: result{
54 ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false},
55 },
56 },
57
58 {
59 args: []string{"nosuchpkg", "errors"},
60 tests: true,
61 want: result{
62 ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true},
63 },
64 },
65
66 {
67 args: []string{"fmt", "errors", "--", "surplus"},
68 want: result{
69 Rest: []string{"surplus"},
70 ImportPkgs: map[string]bool{"errors": false, "fmt": false},
71 },
72 },
73
74 {
75 args: []string{"foo.go", "bar.go"},
76 want: result{CreatePkgs: []loader.PkgSpec{{
77 Filenames: []string{"foo.go", "bar.go"},
78 }}},
79 },
80
81 {
82 args: []string{"foo.go", "fmt"},
83 want: result{
84 Err: "named files must be .go files: fmt",
85 },
86 },
87 } {
88 var conf loader.Config
89 rest, err := conf.FromArgs(test.args, test.tests)
90 got := result{
91 Rest: rest,
92 ImportPkgs: conf.ImportPkgs,
93 CreatePkgs: conf.CreatePkgs,
94 }
95 if err != nil {
96 got.Err = err.Error()
97 }
98 if !reflect.DeepEqual(got, test.want) {
99 t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want)
100 }
101 }
102 }
103
104 func TestLoad_NoInitialPackages(t *testing.T) {
105 var conf loader.Config
106
107 const wantErr = "no initial packages were loaded"
108
109 prog, err := conf.Load()
110 if err == nil {
111 t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
112 } else if err.Error() != wantErr {
113 t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
114 }
115 if prog != nil {
116 t.Errorf("Load unexpectedly returned a Program")
117 }
118 }
119
120 func TestLoad_MissingInitialPackage(t *testing.T) {
121 var conf loader.Config
122 conf.Import("nosuchpkg")
123 conf.Import("errors")
124
125 const wantErr = "couldn't load packages due to errors: nosuchpkg"
126
127 prog, err := conf.Load()
128 if err == nil {
129 t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
130 } else if err.Error() != wantErr {
131 t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
132 }
133 if prog != nil {
134 t.Errorf("Load unexpectedly returned a Program")
135 }
136 }
137
138 func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) {
139 if runtime.Compiler == "gccgo" {
140 t.Skip("gccgo has no standard library test files")
141 }
142
143 var conf loader.Config
144 conf.AllowErrors = true
145 conf.Import("nosuchpkg")
146 conf.ImportWithTests("errors")
147
148 prog, err := conf.Load()
149 if err != nil {
150 t.Errorf("Load failed unexpectedly: %v", err)
151 }
152 if prog == nil {
153 t.Fatalf("Load returned a nil Program")
154 }
155 if got, want := created(prog), "errors_test"; got != want {
156 t.Errorf("Created = %s, want %s", got, want)
157 }
158 if got, want := imported(prog), "errors"; got != want {
159 t.Errorf("Imported = %s, want %s", got, want)
160 }
161 }
162
163 func TestCreateUnnamedPackage(t *testing.T) {
164 var conf loader.Config
165 conf.CreateFromFilenames("")
166 prog, err := conf.Load()
167 if err != nil {
168 t.Fatalf("Load failed: %v", err)
169 }
170 if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
171 t.Errorf("InitialPackages = %s, want %s", got, want)
172 }
173 }
174
175 func TestLoad_MissingFileInCreatedPackage(t *testing.T) {
176 var conf loader.Config
177 conf.CreateFromFilenames("", "missing.go")
178
179 const wantErr = "couldn't load packages due to errors: (unnamed)"
180
181 prog, err := conf.Load()
182 if prog != nil {
183 t.Errorf("Load unexpectedly returned a Program")
184 }
185 if err == nil {
186 t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
187 }
188 if err.Error() != wantErr {
189 t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
190 }
191 }
192
193 func TestLoad_MissingFileInCreatedPackage_AllowErrors(t *testing.T) {
194 conf := loader.Config{AllowErrors: true}
195 conf.CreateFromFilenames("", "missing.go")
196
197 prog, err := conf.Load()
198 if err != nil {
199 t.Errorf("Load failed: %v", err)
200 }
201 if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
202 t.Fatalf("InitialPackages = %s, want %s", got, want)
203 }
204 }
205
206 func TestLoad_ParseError(t *testing.T) {
207 var conf loader.Config
208 conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
209
210 const wantErr = "couldn't load packages due to errors: badpkg"
211
212 prog, err := conf.Load()
213 if prog != nil {
214 t.Errorf("Load unexpectedly returned a Program")
215 }
216 if err == nil {
217 t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
218 }
219 if err.Error() != wantErr {
220 t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
221 }
222 }
223
224 func TestLoad_ParseError_AllowErrors(t *testing.T) {
225 var conf loader.Config
226 conf.AllowErrors = true
227 conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
228
229 prog, err := conf.Load()
230 if err != nil {
231 t.Errorf("Load failed unexpectedly: %v", err)
232 }
233 if prog == nil {
234 t.Fatalf("Load returned a nil Program")
235 }
236 if got, want := created(prog), "badpkg"; got != want {
237 t.Errorf("Created = %s, want %s", got, want)
238 }
239
240 badpkg := prog.Created[0]
241 if len(badpkg.Files) != 1 {
242 t.Errorf("badpkg has %d files, want 1", len(badpkg.Files))
243 }
244 wantErr := filepath.Join("testdata", "badpkgdecl.go") + ":1:34: expected 'package', found 'EOF'"
245 if !hasError(badpkg.Errors, wantErr) {
246 t.Errorf("badpkg.Errors = %v, want %s", badpkg.Errors, wantErr)
247 }
248 }
249
250 func TestLoad_FromSource_Success(t *testing.T) {
251 var conf loader.Config
252 conf.CreateFromFilenames("P", "testdata/a.go", "testdata/b.go")
253
254 prog, err := conf.Load()
255 if err != nil {
256 t.Errorf("Load failed unexpectedly: %v", err)
257 }
258 if prog == nil {
259 t.Fatalf("Load returned a nil Program")
260 }
261 if got, want := created(prog), "P"; got != want {
262 t.Errorf("Created = %s, want %s", got, want)
263 }
264 }
265
266 func TestLoad_FromImports_Success(t *testing.T) {
267 if runtime.Compiler == "gccgo" {
268 t.Skip("gccgo has no standard library test files")
269 }
270
271 var conf loader.Config
272 conf.ImportWithTests("fmt")
273 conf.ImportWithTests("errors")
274
275 prog, err := conf.Load()
276 if err != nil {
277 t.Errorf("Load failed unexpectedly: %v", err)
278 }
279 if prog == nil {
280 t.Fatalf("Load returned a nil Program")
281 }
282 if got, want := created(prog), "errors_test fmt_test"; got != want {
283 t.Errorf("Created = %q, want %s", got, want)
284 }
285 if got, want := imported(prog), "errors fmt"; got != want {
286 t.Errorf("Imported = %s, want %s", got, want)
287 }
288
289
290 want := map[string]bool{
291 "strings": true,
292 "time": true,
293 "runtime": true,
294 "testing": true,
295 "unicode": true,
296 }
297 for _, path := range all(prog) {
298 delete(want, path)
299 }
300 if len(want) > 0 {
301 t.Errorf("AllPackages is missing these keys: %q", keys(want))
302 }
303 }
304
305 func TestLoad_MissingIndirectImport(t *testing.T) {
306 pkgs := map[string]string{
307 "a": `package a; import _ "b"`,
308 "b": `package b; import _ "c"`,
309 }
310 conf := loader.Config{Build: fakeContext(pkgs)}
311 conf.Import("a")
312
313 const wantErr = "couldn't load packages due to errors: b"
314
315 prog, err := conf.Load()
316 if err == nil {
317 t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
318 } else if err.Error() != wantErr {
319 t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
320 }
321 if prog != nil {
322 t.Errorf("Load unexpectedly returned a Program")
323 }
324 }
325
326 func TestLoad_BadDependency_AllowErrors(t *testing.T) {
327 for _, test := range []struct {
328 descr string
329 pkgs map[string]string
330 wantPkgs string
331 }{
332
333 {
334 descr: "missing dependency",
335 pkgs: map[string]string{
336 "a": `package a; import _ "b"`,
337 "b": `package b; import _ "c"`,
338 },
339 wantPkgs: "a b",
340 },
341 {
342 descr: "bad package decl in dependency",
343 pkgs: map[string]string{
344 "a": `package a; import _ "b"`,
345 "b": `package b; import _ "c"`,
346 "c": `package`,
347 },
348 wantPkgs: "a b",
349 },
350 {
351 descr: "parse error in dependency",
352 pkgs: map[string]string{
353 "a": `package a; import _ "b"`,
354 "b": `package b; import _ "c"`,
355 "c": `package c; var x = `,
356 },
357 wantPkgs: "a b c",
358 },
359 } {
360 conf := loader.Config{
361 AllowErrors: true,
362 Build: fakeContext(test.pkgs),
363 }
364 conf.Import("a")
365
366 prog, err := conf.Load()
367 if err != nil {
368 t.Errorf("%s: Load failed unexpectedly: %v", test.descr, err)
369 }
370 if prog == nil {
371 t.Fatalf("%s: Load returned a nil Program", test.descr)
372 }
373
374 if got, want := imported(prog), "a"; got != want {
375 t.Errorf("%s: Imported = %s, want %s", test.descr, got, want)
376 }
377 if got := all(prog); strings.Join(got, " ") != test.wantPkgs {
378 t.Errorf("%s: AllPackages = %s, want %s", test.descr, got, test.wantPkgs)
379 }
380 }
381 }
382
383 func TestCwd(t *testing.T) {
384 ctxt := fakeContext(map[string]string{"one/two/three": `package three`})
385 for _, test := range []struct {
386 cwd, arg, want string
387 }{
388 {cwd: "/go/src/one", arg: "./two/three", want: "one/two/three"},
389 {cwd: "/go/src/one", arg: "../one/two/three", want: "one/two/three"},
390 {cwd: "/go/src/one", arg: "one/two/three", want: "one/two/three"},
391 {cwd: "/go/src/one/two/three", arg: ".", want: "one/two/three"},
392 {cwd: "/go/src/one", arg: "two/three", want: ""},
393 } {
394 conf := loader.Config{
395 Cwd: test.cwd,
396 Build: ctxt,
397 }
398 conf.Import(test.arg)
399
400 var got string
401 prog, err := conf.Load()
402 if prog != nil {
403 got = imported(prog)
404 }
405 if got != test.want {
406 t.Errorf("Load(%s) from %s: Imported = %s, want %s",
407 test.arg, test.cwd, got, test.want)
408 if err != nil {
409 t.Errorf("Load failed: %v", err)
410 }
411 }
412 }
413 }
414
415 func TestLoad_vendor(t *testing.T) {
416 pkgs := map[string]string{
417 "a": `package a; import _ "x"`,
418 "a/vendor": ``,
419 "a/vendor/x": `package xa`,
420 "b": `package b; import _ "x"`,
421 "b/vendor": ``,
422 "b/vendor/x": `package xb`,
423 "c": `package c; import _ "x"`,
424 "x": `package xc`,
425 }
426 conf := loader.Config{Build: fakeContext(pkgs)}
427 conf.Import("a")
428 conf.Import("b")
429 conf.Import("c")
430
431 prog, err := conf.Load()
432 if err != nil {
433 t.Fatal(err)
434 }
435
436
437 for _, r := range "abc" {
438 name := string(r)
439 got := prog.Package(name).Pkg.Imports()[0]
440 want := "x" + name
441 if got.Name() != want {
442 t.Errorf("package %s import %q = %s, want %s",
443 name, "x", got.Name(), want)
444 }
445 }
446 }
447
448 func TestVendorCwd(t *testing.T) {
449
450 ctxt := fakeContext(map[string]string{
451 "net": ``,
452 "net/http": `package http; import _ "hpack"`,
453 "vendor": ``,
454 "vendor/hpack": `package vendorhpack`,
455 "hpack": `package hpack`,
456 })
457 for i, test := range []struct {
458 cwd, arg, want string
459 }{
460 {cwd: "/go/src/net", arg: "http"},
461 {cwd: "/go/src/net", arg: "./http", want: "net/http vendor/hpack"},
462 {cwd: "/go/src/net", arg: "hpack", want: "vendor/hpack"},
463 {cwd: "/go/src/vendor", arg: "hpack", want: "vendor/hpack"},
464 {cwd: "/go/src/vendor", arg: "./hpack", want: "vendor/hpack"},
465 } {
466 conf := loader.Config{
467 Cwd: test.cwd,
468 Build: ctxt,
469 }
470 conf.Import(test.arg)
471
472 var got string
473 prog, err := conf.Load()
474 if prog != nil {
475 got = strings.Join(all(prog), " ")
476 }
477 if got != test.want {
478 t.Errorf("#%d: Load(%s) from %s: got %s, want %s",
479 i, test.arg, test.cwd, got, test.want)
480 if err != nil {
481 t.Errorf("Load failed: %v", err)
482 }
483 }
484 }
485 }
486
487 func TestVendorCwdIssue16580(t *testing.T) {
488
489
490
491 ctxt := fakeContext(map[string]string{
492 "a": ``,
493 "a/vendor": ``,
494 "a/vendor/b": `package b; const X = true`,
495 "b": `package b; const X = false`,
496 })
497 for _, test := range []struct {
498 filename, cwd string
499 want bool
500 }{
501 {filename: "c.go", cwd: "/go/src", want: false},
502 {filename: "c.go", cwd: "/go/src/a", want: false},
503 {filename: "c.go", cwd: "/go/src/a/b", want: false},
504 {filename: "c.go", cwd: "/go/src/a/vendor/b", want: false},
505
506 {filename: "/go/src/a/c.go", cwd: "/go/src", want: true},
507 {filename: "/go/src/a/c.go", cwd: "/go/src/a", want: true},
508 {filename: "/go/src/a/c.go", cwd: "/go/src/a/b", want: true},
509 {filename: "/go/src/a/c.go", cwd: "/go/src/a/vendor/b", want: true},
510
511 {filename: "/go/src/c/c.go", cwd: "/go/src", want: false},
512 {filename: "/go/src/c/c.go", cwd: "/go/src/a", want: false},
513 {filename: "/go/src/c/c.go", cwd: "/go/src/a/b", want: false},
514 {filename: "/go/src/c/c.go", cwd: "/go/src/a/vendor/b", want: false},
515 } {
516 conf := loader.Config{
517 Cwd: test.cwd,
518 Build: ctxt,
519 }
520 f, err := conf.ParseFile(test.filename, `package dummy; import "b"; const X = b.X`)
521 if err != nil {
522 t.Fatal(f)
523 }
524 conf.CreateFromFiles("dummy", f)
525
526 prog, err := conf.Load()
527 if err != nil {
528 t.Errorf("%+v: Load failed: %v", test, err)
529 continue
530 }
531
532 x := constant.BoolVal(prog.Created[0].Pkg.Scope().Lookup("X").(*types.Const).Val())
533 if x != test.want {
534 t.Errorf("%+v: b.X = %t", test, x)
535 }
536 }
537
538
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556 func TestTransitivelyErrorFreeFlag(t *testing.T) {
557
558
559
560
561
562
563
564
565 pkgs := map[string]string{
566 "a": `package a; import (_ "b"; _ "e")`,
567 "b": `package b; import _ "c"`,
568 "c": `package c; func f() { _ = int(false) }`,
569 "d": `package d;`,
570 "e": `package e; import _ "d"`,
571 }
572 conf := loader.Config{
573 AllowErrors: true,
574 Build: fakeContext(pkgs),
575 }
576 conf.Import("a")
577
578 prog, err := conf.Load()
579 if err != nil {
580 t.Errorf("Load failed: %s", err)
581 }
582 if prog == nil {
583 t.Fatalf("Load returned nil *Program")
584 }
585
586 for pkg, info := range prog.AllPackages {
587 var wantErr, wantTEF bool
588 switch pkg.Path() {
589 case "a", "b":
590 case "c":
591 wantErr = true
592 case "d", "e":
593 wantTEF = true
594 default:
595 t.Errorf("unexpected package: %q", pkg.Path())
596 continue
597 }
598
599 if (info.Errors != nil) != wantErr {
600 if wantErr {
601 t.Errorf("Package %q.Error = nil, want error", pkg.Path())
602 } else {
603 t.Errorf("Package %q has unexpected Errors: %v",
604 pkg.Path(), info.Errors)
605 }
606 }
607
608 if info.TransitivelyErrorFree != wantTEF {
609 t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t",
610 pkg.Path(), info.TransitivelyErrorFree, wantTEF)
611 }
612 }
613 }
614
615
616
617 func TestErrorReporting(t *testing.T) {
618 pkgs := map[string]string{
619 "a": `package a; import (_ "b"; _ "c"); var x int = false`,
620 "b": `package b; 'syntax error!`,
621 }
622 conf := loader.Config{
623 AllowErrors: true,
624 Build: fakeContext(pkgs),
625 }
626 var mu sync.Mutex
627 var allErrors []error
628 conf.TypeChecker.Error = func(err error) {
629 mu.Lock()
630 allErrors = append(allErrors, err)
631 mu.Unlock()
632 }
633 conf.Import("a")
634
635 prog, err := conf.Load()
636 if err != nil {
637 t.Errorf("Load failed: %s", err)
638 }
639 if prog == nil {
640 t.Fatalf("Load returned nil *Program")
641 }
642
643
644
645
646 for pkg, info := range prog.AllPackages {
647 switch pkg.Path() {
648 case "a":
649
650
651
652 if !hasError(info.Errors, "cannot") {
653 t.Errorf("a.Errors = %v, want bool assignment (type) error", info.Errors)
654 }
655 if !hasError(info.Errors, "could not import c") {
656 t.Errorf("a.Errors = %v, want import (loader) error", info.Errors)
657 }
658 case "b":
659 if !hasError(info.Errors, "rune literal not terminated") {
660 t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors)
661 }
662 }
663 }
664
665
666 if !hasError(allErrors, "cannot") ||
667 !hasError(allErrors, "rune literal not terminated") ||
668 !hasError(allErrors, "could not import c") {
669 t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors)
670 }
671 }
672
673 func TestCycles(t *testing.T) {
674 for _, test := range []struct {
675 descr string
676 ctxt *build.Context
677 wantErr string
678 }{
679 {
680 "self-cycle",
681 fakeContext(map[string]string{
682 "main": `package main; import _ "selfcycle"`,
683 "selfcycle": `package selfcycle; import _ "selfcycle"`,
684 }),
685 `import cycle: selfcycle -> selfcycle`,
686 },
687 {
688 "three-package cycle",
689 fakeContext(map[string]string{
690 "main": `package main; import _ "a"`,
691 "a": `package a; import _ "b"`,
692 "b": `package b; import _ "c"`,
693 "c": `package c; import _ "a"`,
694 }),
695 `import cycle: c -> a -> b -> c`,
696 },
697 {
698 "self-cycle in dependency of test file",
699 buildutil.FakeContext(map[string]map[string]string{
700 "main": {
701 "main.go": `package main`,
702 "main_test.go": `package main; import _ "a"`,
703 },
704 "a": {
705 "a.go": `package a; import _ "a"`,
706 },
707 }),
708 `import cycle: a -> a`,
709 },
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734 } {
735 conf := loader.Config{
736 AllowErrors: true,
737 Build: test.ctxt,
738 }
739 var mu sync.Mutex
740 var allErrors []error
741 conf.TypeChecker.Error = func(err error) {
742 mu.Lock()
743 allErrors = append(allErrors, err)
744 mu.Unlock()
745 }
746 conf.ImportWithTests("main")
747
748 prog, err := conf.Load()
749 if err != nil {
750 t.Errorf("%s: Load failed: %s", test.descr, err)
751 }
752 if prog == nil {
753 t.Fatalf("%s: Load returned nil *Program", test.descr)
754 }
755
756 if !hasError(allErrors, test.wantErr) {
757 t.Errorf("%s: Load() errors = %q, want %q",
758 test.descr, allErrors, test.wantErr)
759 }
760 }
761
762
763
764
765 }
766
767
768
769
770 func fakeContext(pkgs map[string]string) *build.Context {
771 pkgs2 := make(map[string]map[string]string)
772 for path, content := range pkgs {
773 pkgs2[path] = map[string]string{"x.go": content}
774 }
775 return buildutil.FakeContext(pkgs2)
776 }
777
778 func hasError(errors []error, substr string) bool {
779 for _, err := range errors {
780 if strings.Contains(err.Error(), substr) {
781 return true
782 }
783 }
784 return false
785 }
786
787 func keys(m map[string]bool) (keys []string) {
788 for key := range m {
789 keys = append(keys, key)
790 }
791 sort.Strings(keys)
792 return
793 }
794
795
796 func all(prog *loader.Program) []string {
797 var pkgs []string
798 for _, info := range prog.AllPackages {
799 pkgs = append(pkgs, info.Pkg.Path())
800 }
801 sort.Strings(pkgs)
802 return pkgs
803 }
804
805
806 func imported(prog *loader.Program) string {
807 var pkgs []string
808 for _, info := range prog.Imported {
809 pkgs = append(pkgs, info.Pkg.Path())
810 }
811 sort.Strings(pkgs)
812 return strings.Join(pkgs, " ")
813 }
814
815
816 func created(prog *loader.Program) string {
817 var pkgs []string
818 for _, info := range prog.Created {
819 pkgs = append(pkgs, info.Pkg.Path())
820 }
821 return strings.Join(pkgs, " ")
822 }
823
824
825
826
827 func TestLoad1(t *testing.T) { loadIO(t) }
828 func TestLoad2(t *testing.T) { loadIO(t) }
829
830 func loadIO(t *testing.T) {
831 t.Parallel()
832 conf := &loader.Config{ImportPkgs: map[string]bool{"io": false}}
833 if _, err := conf.Load(); err != nil {
834 t.Fatal(err)
835 }
836 }
837
838 func TestCgoCwdIssue46877(t *testing.T) {
839 testenv.NeedsTool(t, "go")
840 testenv.NeedsTool(t, "cgo")
841 var conf loader.Config
842 conf.Import("golang.org/x/tools/go/loader/testdata/issue46877")
843 if _, err := conf.Load(); err != nil {
844 t.Errorf("Load failed: %v", err)
845 }
846 }
847
View as plain text