github.com/Secbyte/godog@v0.7.14-0.20200116175429-d8f0aeeb70cf/suite.go (about) 1 package godog 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "math/rand" 8 "os" 9 "path/filepath" 10 "reflect" 11 "regexp" 12 "sort" 13 "strconv" 14 "strings" 15 "unicode/utf8" 16 17 "github.com/DATA-DOG/godog/gherkin" 18 ) 19 20 var errorInterface = reflect.TypeOf((*error)(nil)).Elem() 21 var typeOfBytes = reflect.TypeOf([]byte(nil)) 22 23 type feature struct { 24 *gherkin.Feature 25 Content []byte `json:"-"` 26 Path string `json:"path"` 27 order int 28 } 29 30 // ErrUndefined is returned in case if step definition was not found 31 var ErrUndefined = fmt.Errorf("step is undefined") 32 33 // ErrPending should be returned by step definition if 34 // step implementation is pending 35 var ErrPending = fmt.Errorf("step implementation is pending") 36 37 // Suite allows various contexts 38 // to register steps and event handlers. 39 // 40 // When running a test suite, the instance of Suite 41 // is passed to all functions (contexts), which 42 // have it as a first and only argument. 43 // 44 // Note that all event hooks does not catch panic errors 45 // in order to have a trace information. Only step 46 // executions are catching panic error since it may 47 // be a context specific error. 48 type Suite struct { 49 steps []*StepDef 50 features []*feature 51 fmt Formatter 52 53 failed bool 54 randomSeed int64 55 stopOnFailure bool 56 strict bool 57 58 // suite event handlers 59 beforeSuiteHandlers []func() 60 beforeFeatureHandlers []func(*gherkin.Feature) 61 beforeScenarioHandlers []func(interface{}) 62 beforeStepHandlers []func(*gherkin.Step) 63 afterStepHandlers []func(*gherkin.Step, error) 64 afterScenarioHandlers []func(interface{}, error) 65 afterFeatureHandlers []func(*gherkin.Feature) 66 afterSuiteHandlers []func() 67 } 68 69 // Step allows to register a *StepDef in Godog 70 // feature suite, the definition will be applied 71 // to all steps matching the given Regexp expr. 72 // 73 // It will panic if expr is not a valid regular 74 // expression or stepFunc is not a valid step 75 // handler. 76 // 77 // Note that if there are two definitions which may match 78 // the same step, then only the first matched handler 79 // will be applied. 80 // 81 // If none of the *StepDef is matched, then 82 // ErrUndefined error will be returned when 83 // running steps. 84 func (s *Suite) Step(expr interface{}, stepFunc interface{}) { 85 var regex *regexp.Regexp 86 87 switch t := expr.(type) { 88 case *regexp.Regexp: 89 regex = t 90 case string: 91 regex = regexp.MustCompile(t) 92 case []byte: 93 regex = regexp.MustCompile(string(t)) 94 default: 95 panic(fmt.Sprintf("expecting expr to be a *regexp.Regexp or a string, got type: %T", expr)) 96 } 97 98 v := reflect.ValueOf(stepFunc) 99 typ := v.Type() 100 if typ.Kind() != reflect.Func { 101 panic(fmt.Sprintf("expected handler to be func, but got: %T", stepFunc)) 102 } 103 104 if typ.NumOut() != 1 { 105 panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut())) 106 } 107 108 def := &StepDef{ 109 Handler: stepFunc, 110 Expr: regex, 111 hv: v, 112 } 113 114 typ = typ.Out(0) 115 switch typ.Kind() { 116 case reflect.Interface: 117 if !typ.Implements(errorInterface) { 118 panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind())) 119 } 120 case reflect.Slice: 121 if typ.Elem().Kind() != reflect.String { 122 panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind())) 123 } 124 def.nested = true 125 default: 126 panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind())) 127 } 128 129 s.steps = append(s.steps, def) 130 } 131 132 // BeforeSuite registers a function or method 133 // to be run once before suite runner. 134 // 135 // Use it to prepare the test suite for a spin. 136 // Connect and prepare database for instance... 137 func (s *Suite) BeforeSuite(fn func()) { 138 s.beforeSuiteHandlers = append(s.beforeSuiteHandlers, fn) 139 } 140 141 // BeforeFeature registers a function or method 142 // to be run once before every feature execution. 143 // 144 // If godog is run with concurrency option, it will 145 // run every feature per goroutine. So user may choose 146 // whether to isolate state within feature context or 147 // scenario. 148 // 149 // Best practice is not to have any state dependency on 150 // every scenario, but in some cases if VM for example 151 // needs to be started it may take very long for each 152 // scenario to restart it. 153 // 154 // Use it wisely and avoid sharing state between scenarios. 155 func (s *Suite) BeforeFeature(fn func(*gherkin.Feature)) { 156 s.beforeFeatureHandlers = append(s.beforeFeatureHandlers, fn) 157 } 158 159 // BeforeScenario registers a function or method 160 // to be run before every scenario or scenario outline. 161 // 162 // The interface argument may be *gherkin.Scenario 163 // or *gherkin.ScenarioOutline 164 // 165 // It is a good practice to restore the default state 166 // before every scenario so it would be isolated from 167 // any kind of state. 168 func (s *Suite) BeforeScenario(fn func(interface{})) { 169 s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn) 170 } 171 172 // BeforeStep registers a function or method 173 // to be run before every scenario 174 func (s *Suite) BeforeStep(fn func(*gherkin.Step)) { 175 s.beforeStepHandlers = append(s.beforeStepHandlers, fn) 176 } 177 178 // AfterStep registers an function or method 179 // to be run after every scenario 180 // 181 // It may be convenient to return a different kind of error 182 // in order to print more state details which may help 183 // in case of step failure 184 // 185 // In some cases, for example when running a headless 186 // browser, to take a screenshot after failure. 187 func (s *Suite) AfterStep(fn func(*gherkin.Step, error)) { 188 s.afterStepHandlers = append(s.afterStepHandlers, fn) 189 } 190 191 // AfterScenario registers an function or method 192 // to be run after every scenario or scenario outline 193 // 194 // The interface argument may be *gherkin.Scenario 195 // or *gherkin.ScenarioOutline 196 func (s *Suite) AfterScenario(fn func(interface{}, error)) { 197 s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn) 198 } 199 200 // AfterFeature registers a function or method 201 // to be run once after feature executed all scenarios. 202 func (s *Suite) AfterFeature(fn func(*gherkin.Feature)) { 203 s.afterFeatureHandlers = append(s.afterFeatureHandlers, fn) 204 } 205 206 // AfterSuite registers a function or method 207 // to be run once after suite runner 208 func (s *Suite) AfterSuite(fn func()) { 209 s.afterSuiteHandlers = append(s.afterSuiteHandlers, fn) 210 } 211 212 func (s *Suite) run() { 213 // run before suite handlers 214 for _, f := range s.beforeSuiteHandlers { 215 f() 216 } 217 // run features 218 for _, f := range s.features { 219 s.runFeature(f) 220 if s.failed && s.stopOnFailure { 221 // stop on first failure 222 break 223 } 224 } 225 // run after suite handlers 226 for _, f := range s.afterSuiteHandlers { 227 f() 228 } 229 } 230 231 func (s *Suite) matchStep(step *gherkin.Step) *StepDef { 232 def := s.matchStepText(step.Text) 233 if def != nil && step.Argument != nil { 234 def.args = append(def.args, step.Argument) 235 } 236 return def 237 } 238 239 func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) { 240 // run before step handlers 241 for _, f := range s.beforeStepHandlers { 242 f(step) 243 } 244 245 match := s.matchStep(step) 246 s.fmt.Defined(step, match) 247 248 // user multistep definitions may panic 249 defer func() { 250 if e := recover(); e != nil { 251 err = &traceError{ 252 msg: fmt.Sprintf("%v", e), 253 stack: callStack(), 254 } 255 } 256 257 if prevStepErr != nil { 258 return 259 } 260 261 if err == ErrUndefined { 262 return 263 } 264 265 switch err { 266 case nil: 267 s.fmt.Passed(step, match) 268 case ErrPending: 269 s.fmt.Pending(step, match) 270 default: 271 s.fmt.Failed(step, match, err) 272 } 273 274 // run after step handlers 275 for _, f := range s.afterStepHandlers { 276 f(step, err) 277 } 278 }() 279 280 if undef, err := s.maybeUndefined(step.Text, step.Argument); err != nil { 281 return err 282 } else if len(undef) > 0 { 283 if match != nil { 284 match = &StepDef{ 285 args: match.args, 286 hv: match.hv, 287 Expr: match.Expr, 288 Handler: match.Handler, 289 nested: match.nested, 290 undefined: undef, 291 } 292 } 293 s.fmt.Undefined(step, match) 294 return ErrUndefined 295 } 296 297 if prevStepErr != nil { 298 s.fmt.Skipped(step, match) 299 return nil 300 } 301 302 err = s.maybeSubSteps(match.run()) 303 return 304 } 305 306 func (s *Suite) maybeUndefined(text string, arg interface{}) ([]string, error) { 307 step := s.matchStepText(text) 308 if nil == step { 309 return []string{text}, nil 310 } 311 312 var undefined []string 313 if !step.nested { 314 return undefined, nil 315 } 316 317 if arg != nil { 318 step.args = append(step.args, arg) 319 } 320 321 for _, next := range step.run().(Steps) { 322 lines := strings.Split(next, "\n") 323 // @TODO: we cannot currently parse table or content body from nested steps 324 if len(lines) > 1 { 325 return undefined, fmt.Errorf("nested steps cannot be multiline and have table or content body argument") 326 } 327 if len(lines[0]) > 0 && lines[0][len(lines[0])-1] == ':' { 328 return undefined, fmt.Errorf("nested steps cannot be multiline and have table or content body argument") 329 } 330 undef, err := s.maybeUndefined(next, nil) 331 if err != nil { 332 return undefined, err 333 } 334 undefined = append(undefined, undef...) 335 } 336 return undefined, nil 337 } 338 339 func (s *Suite) maybeSubSteps(result interface{}) error { 340 if nil == result { 341 return nil 342 } 343 344 if err, ok := result.(error); ok { 345 return err 346 } 347 348 steps, ok := result.(Steps) 349 if !ok { 350 return fmt.Errorf("unexpected error, should have been []string: %T - %+v", result, result) 351 } 352 353 for _, text := range steps { 354 if def := s.matchStepText(text); def == nil { 355 return ErrUndefined 356 } else if err := s.maybeSubSteps(def.run()); err != nil { 357 return fmt.Errorf("%s: %+v", text, err) 358 } 359 } 360 return nil 361 } 362 363 func (s *Suite) matchStepText(text string) *StepDef { 364 for _, h := range s.steps { 365 if m := h.Expr.FindStringSubmatch(text); len(m) > 0 { 366 var args []interface{} 367 for _, m := range m[1:] { 368 args = append(args, m) 369 } 370 371 // since we need to assign arguments 372 // better to copy the step definition 373 return &StepDef{ 374 args: args, 375 hv: h.hv, 376 Expr: h.Expr, 377 Handler: h.Handler, 378 nested: h.nested, 379 } 380 } 381 } 382 return nil 383 } 384 385 func (s *Suite) runSteps(steps []*gherkin.Step) (err error) { 386 for _, step := range steps { 387 stepErr := s.runStep(step, err) 388 switch stepErr { 389 case ErrUndefined: 390 // do not overwrite failed error 391 if err == ErrUndefined || err == nil { 392 err = stepErr 393 } 394 case ErrPending: 395 err = stepErr 396 case nil: 397 default: 398 err = stepErr 399 } 400 } 401 return 402 } 403 404 func (s *Suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Background) (failErr error) { 405 s.fmt.Node(outline) 406 407 for _, ex := range outline.Examples { 408 example, hasExamples := examples(ex) 409 if !hasExamples { 410 // @TODO: may need to print empty example node, but 411 // for backward compatibility, cannot cast to *gherkin.ExamplesBase 412 // at the moment 413 continue 414 } 415 416 s.fmt.Node(example) 417 placeholders := example.TableHeader.Cells 418 groups := example.TableBody 419 420 for _, group := range groups { 421 if !isEmptyScenario(outline) { 422 for _, f := range s.beforeScenarioHandlers { 423 f(outline) 424 } 425 } 426 var steps []*gherkin.Step 427 for _, outlineStep := range outline.Steps { 428 text := outlineStep.Text 429 for i, placeholder := range placeholders { 430 text = strings.Replace(text, "<"+placeholder.Value+">", group.Cells[i].Value, -1) 431 } 432 433 // translate argument 434 arg := outlineStep.Argument 435 switch t := outlineStep.Argument.(type) { 436 case *gherkin.DataTable: 437 tbl := &gherkin.DataTable{ 438 Node: t.Node, 439 Rows: make([]*gherkin.TableRow, len(t.Rows)), 440 } 441 for i, row := range t.Rows { 442 cells := make([]*gherkin.TableCell, len(row.Cells)) 443 for j, cell := range row.Cells { 444 trans := cell.Value 445 for i, placeholder := range placeholders { 446 trans = strings.Replace(trans, "<"+placeholder.Value+">", group.Cells[i].Value, -1) 447 } 448 cells[j] = &gherkin.TableCell{ 449 Node: cell.Node, 450 Value: trans, 451 } 452 } 453 tbl.Rows[i] = &gherkin.TableRow{ 454 Node: row.Node, 455 Cells: cells, 456 } 457 } 458 arg = tbl 459 case *gherkin.DocString: 460 trans := t.Content 461 for i, placeholder := range placeholders { 462 trans = strings.Replace(trans, "<"+placeholder.Value+">", group.Cells[i].Value, -1) 463 } 464 arg = &gherkin.DocString{ 465 Node: t.Node, 466 Content: trans, 467 ContentType: t.ContentType, 468 Delimitter: t.Delimitter, 469 } 470 } 471 472 // clone a step 473 step := &gherkin.Step{ 474 Node: outlineStep.Node, 475 Text: text, 476 Keyword: outlineStep.Keyword, 477 Argument: arg, 478 } 479 steps = append(steps, step) 480 } 481 482 // run example table row 483 s.fmt.Node(group) 484 485 if b != nil { 486 steps = append(b.Steps, steps...) 487 } 488 489 err := s.runSteps(steps) 490 491 if !isEmptyScenario(outline) { 492 for _, f := range s.afterScenarioHandlers { 493 f(outline, err) 494 } 495 } 496 497 if s.shouldFail(err) { 498 failErr = err 499 if s.stopOnFailure { 500 return 501 } 502 } 503 } 504 } 505 return 506 } 507 508 func (s *Suite) shouldFail(err error) bool { 509 if err == nil { 510 return false 511 } 512 513 if err == ErrUndefined || err == ErrPending { 514 return s.strict 515 } 516 517 return true 518 } 519 520 func (s *Suite) runFeature(f *feature) { 521 if !isEmptyFeature(f.Feature) { 522 for _, fn := range s.beforeFeatureHandlers { 523 fn(f.Feature) 524 } 525 } 526 527 s.fmt.Feature(f.Feature, f.Path, f.Content) 528 529 // make a local copy of the feature scenario defenitions, 530 // then shuffle it if we are randomizing scenarios 531 scenarios := make([]interface{}, len(f.ScenarioDefinitions)) 532 if s.randomSeed != 0 { 533 r := rand.New(rand.NewSource(s.randomSeed)) 534 perm := r.Perm(len(f.ScenarioDefinitions)) 535 for i, v := range perm { 536 scenarios[v] = f.ScenarioDefinitions[i] 537 } 538 } else { 539 copy(scenarios, f.ScenarioDefinitions) 540 } 541 542 defer func() { 543 if !isEmptyFeature(f.Feature) { 544 for _, fn := range s.afterFeatureHandlers { 545 fn(f.Feature) 546 } 547 } 548 }() 549 550 for _, scenario := range scenarios { 551 var err error 552 if f.Background != nil { 553 s.fmt.Node(f.Background) 554 } 555 switch t := scenario.(type) { 556 case *gherkin.ScenarioOutline: 557 err = s.runOutline(t, f.Background) 558 case *gherkin.Scenario: 559 err = s.runScenario(t, f.Background) 560 } 561 if s.shouldFail(err) { 562 s.failed = true 563 if s.stopOnFailure { 564 return 565 } 566 } 567 } 568 } 569 570 func (s *Suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) { 571 if isEmptyScenario(scenario) { 572 s.fmt.Node(scenario) 573 return ErrUndefined 574 } 575 576 // run before scenario handlers 577 for _, f := range s.beforeScenarioHandlers { 578 f(scenario) 579 } 580 581 s.fmt.Node(scenario) 582 583 // background 584 steps := scenario.Steps 585 if b != nil { 586 steps = append(b.Steps, steps...) 587 } 588 589 // scenario 590 err = s.runSteps(steps) 591 592 // run after scenario handlers 593 for _, f := range s.afterScenarioHandlers { 594 f(scenario, err) 595 } 596 597 return 598 } 599 600 func (s *Suite) printStepDefinitions(w io.Writer) { 601 var longest int 602 for _, def := range s.steps { 603 n := utf8.RuneCountInString(def.Expr.String()) 604 if longest < n { 605 longest = n 606 } 607 } 608 for _, def := range s.steps { 609 n := utf8.RuneCountInString(def.Expr.String()) 610 location := def.definitionID() 611 spaces := strings.Repeat(" ", longest-n) 612 fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location)) 613 } 614 if len(s.steps) == 0 { 615 fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..") 616 } 617 } 618 619 var pathLineRe = regexp.MustCompile(`:([\d]+)$`) 620 621 func extractFeaturePathLine(p string) (string, int) { 622 line := -1 623 retPath := p 624 if m := pathLineRe.FindStringSubmatch(p); len(m) > 0 { 625 if i, err := strconv.Atoi(m[1]); err == nil { 626 line = i 627 retPath = p[:strings.LastIndexByte(p, ':')] 628 } 629 } 630 return retPath, line 631 } 632 633 func parseFeatureFile(path string) (*feature, error) { 634 reader, err := os.Open(path) 635 if err != nil { 636 return nil, err 637 } 638 defer reader.Close() 639 640 var buf bytes.Buffer 641 ft, err := gherkin.ParseFeature(io.TeeReader(reader, &buf)) 642 if err != nil { 643 return nil, fmt.Errorf("%s - %v", path, err) 644 } 645 646 return &feature{ 647 Path: path, 648 Feature: ft, 649 Content: buf.Bytes(), 650 }, nil 651 } 652 653 func parseFeatureDir(dir string) ([]*feature, error) { 654 var features []*feature 655 return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error { 656 if err != nil { 657 return err 658 } 659 660 if f.IsDir() { 661 return nil 662 } 663 664 if !strings.HasSuffix(p, ".feature") { 665 return nil 666 } 667 668 feat, err := parseFeatureFile(p) 669 if err != nil { 670 return err 671 } 672 features = append(features, feat) 673 return nil 674 }) 675 } 676 677 func parsePath(path string) ([]*feature, error) { 678 var features []*feature 679 // check if line number is specified 680 var line int 681 path, line = extractFeaturePathLine(path) 682 683 fi, err := os.Stat(path) 684 if err != nil { 685 return features, err 686 } 687 688 if fi.IsDir() { 689 return parseFeatureDir(path) 690 } 691 692 ft, err := parseFeatureFile(path) 693 if err != nil { 694 return features, err 695 } 696 697 // filter scenario by line number 698 var scenarios []interface{} 699 for _, def := range ft.ScenarioDefinitions { 700 var ln int 701 switch t := def.(type) { 702 case *gherkin.Scenario: 703 ln = t.Location.Line 704 case *gherkin.ScenarioOutline: 705 ln = t.Location.Line 706 } 707 if line == -1 || ln == line { 708 scenarios = append(scenarios, def) 709 } 710 } 711 ft.ScenarioDefinitions = scenarios 712 return append(features, ft), nil 713 } 714 715 func parseFeatures(filter string, paths []string) ([]*feature, error) { 716 byPath := make(map[string]*feature) 717 var order int 718 for _, path := range paths { 719 feats, err := parsePath(path) 720 switch { 721 case os.IsNotExist(err): 722 return nil, fmt.Errorf(`feature path "%s" is not available`, path) 723 case os.IsPermission(err): 724 return nil, fmt.Errorf(`feature path "%s" is not accessible`, path) 725 case err != nil: 726 return nil, err 727 } 728 729 for _, ft := range feats { 730 if _, duplicate := byPath[ft.Path]; duplicate { 731 continue 732 } 733 734 ft.order = order 735 order++ 736 byPath[ft.Path] = ft 737 } 738 } 739 return filterFeatures(filter, byPath), nil 740 } 741 742 type sortByOrderGiven []*feature 743 744 func (s sortByOrderGiven) Len() int { return len(s) } 745 func (s sortByOrderGiven) Less(i, j int) bool { return s[i].order < s[j].order } 746 func (s sortByOrderGiven) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 747 748 func filterFeatures(tags string, collected map[string]*feature) (features []*feature) { 749 for _, ft := range collected { 750 applyTagFilter(tags, ft.Feature) 751 features = append(features, ft) 752 } 753 754 sort.Sort(sortByOrderGiven(features)) 755 756 return features 757 } 758 759 func applyTagFilter(tags string, ft *gherkin.Feature) { 760 if len(tags) == 0 { 761 return 762 } 763 764 var scenarios []interface{} 765 for _, scenario := range ft.ScenarioDefinitions { 766 switch t := scenario.(type) { 767 case *gherkin.ScenarioOutline: 768 var allExamples []*gherkin.Examples 769 for _, examples := range t.Examples { 770 if matchesTags(tags, allTags(ft, t, examples)) { 771 allExamples = append(allExamples, examples) 772 } 773 } 774 t.Examples = allExamples 775 if len(t.Examples) > 0 { 776 scenarios = append(scenarios, scenario) 777 } 778 case *gherkin.Scenario: 779 if matchesTags(tags, allTags(ft, t)) { 780 scenarios = append(scenarios, scenario) 781 } 782 } 783 } 784 ft.ScenarioDefinitions = scenarios 785 } 786 787 func allTags(nodes ...interface{}) []string { 788 var tags, tmp []string 789 for _, node := range nodes { 790 var gr []*gherkin.Tag 791 switch t := node.(type) { 792 case *gherkin.Feature: 793 gr = t.Tags 794 case *gherkin.ScenarioOutline: 795 gr = t.Tags 796 case *gherkin.Scenario: 797 gr = t.Tags 798 case *gherkin.Examples: 799 gr = t.Tags 800 } 801 802 for _, gtag := range gr { 803 tag := strings.TrimSpace(gtag.Name) 804 if tag[0] == '@' { 805 tag = tag[1:] 806 } 807 copy(tmp, tags) 808 var found bool 809 for _, tg := range tmp { 810 if tg == tag { 811 found = true 812 break 813 } 814 } 815 if !found { 816 tags = append(tags, tag) 817 } 818 } 819 } 820 return tags 821 } 822 823 func hasTag(tags []string, tag string) bool { 824 for _, t := range tags { 825 if t == tag { 826 return true 827 } 828 } 829 return false 830 } 831 832 // based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters 833 func matchesTags(filter string, tags []string) (ok bool) { 834 ok = true 835 for _, andTags := range strings.Split(filter, "&&") { 836 var okComma bool 837 for _, tag := range strings.Split(andTags, ",") { 838 tag = strings.Replace(strings.TrimSpace(tag), "@", "", -1) 839 if tag[0] == '~' { 840 tag = tag[1:] 841 okComma = !hasTag(tags, tag) || okComma 842 } else { 843 okComma = hasTag(tags, tag) || okComma 844 } 845 } 846 ok = ok && okComma 847 } 848 return 849 } 850 851 // GetFormatter this allows the formatter copy for that instance of the suite to be referenced 852 func (s *Suite) GetFormatter() Formatter { 853 return s.fmt 854 }