You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

731 lines
18 KiB

  1. // Copyright 2014 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package webdav
  5. import (
  6. "fmt"
  7. "math/rand"
  8. "path"
  9. "reflect"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "testing"
  14. "time"
  15. )
  16. func TestWalkToRoot(t *testing.T) {
  17. testCases := []struct {
  18. name string
  19. want []string
  20. }{{
  21. "/a/b/c/d",
  22. []string{
  23. "/a/b/c/d",
  24. "/a/b/c",
  25. "/a/b",
  26. "/a",
  27. "/",
  28. },
  29. }, {
  30. "/a",
  31. []string{
  32. "/a",
  33. "/",
  34. },
  35. }, {
  36. "/",
  37. []string{
  38. "/",
  39. },
  40. }}
  41. for _, tc := range testCases {
  42. var got []string
  43. if !walkToRoot(tc.name, func(name0 string, first bool) bool {
  44. if first != (len(got) == 0) {
  45. t.Errorf("name=%q: first=%t but len(got)==%d", tc.name, first, len(got))
  46. return false
  47. }
  48. got = append(got, name0)
  49. return true
  50. }) {
  51. continue
  52. }
  53. if !reflect.DeepEqual(got, tc.want) {
  54. t.Errorf("name=%q:\ngot %q\nwant %q", tc.name, got, tc.want)
  55. }
  56. }
  57. }
  58. var lockTestDurations = []time.Duration{
  59. infiniteTimeout, // infiniteTimeout means to never expire.
  60. 0, // A zero duration means to expire immediately.
  61. 100 * time.Hour, // A very large duration will not expire in these tests.
  62. }
  63. // lockTestNames are the names of a set of mutually compatible locks. For each
  64. // name fragment:
  65. // - _ means no explicit lock.
  66. // - i means an infinite-depth lock,
  67. // - z means a zero-depth lock,
  68. var lockTestNames = []string{
  69. "/_/_/_/_/z",
  70. "/_/_/i",
  71. "/_/z",
  72. "/_/z/i",
  73. "/_/z/z",
  74. "/_/z/_/i",
  75. "/_/z/_/z",
  76. "/i",
  77. "/z",
  78. "/z/_/i",
  79. "/z/_/z",
  80. }
  81. func lockTestZeroDepth(name string) bool {
  82. switch name[len(name)-1] {
  83. case 'i':
  84. return false
  85. case 'z':
  86. return true
  87. }
  88. panic(fmt.Sprintf("lock name %q did not end with 'i' or 'z'", name))
  89. }
  90. func TestMemLSCanCreate(t *testing.T) {
  91. now := time.Unix(0, 0)
  92. m := NewMemLS().(*memLS)
  93. for _, name := range lockTestNames {
  94. _, err := m.Create(now, LockDetails{
  95. Root: name,
  96. Duration: infiniteTimeout,
  97. ZeroDepth: lockTestZeroDepth(name),
  98. })
  99. if err != nil {
  100. t.Fatalf("creating lock for %q: %v", name, err)
  101. }
  102. }
  103. wantCanCreate := func(name string, zeroDepth bool) bool {
  104. for _, n := range lockTestNames {
  105. switch {
  106. case n == name:
  107. // An existing lock has the same name as the proposed lock.
  108. return false
  109. case strings.HasPrefix(n, name):
  110. // An existing lock would be a child of the proposed lock,
  111. // which conflicts if the proposed lock has infinite depth.
  112. if !zeroDepth {
  113. return false
  114. }
  115. case strings.HasPrefix(name, n):
  116. // An existing lock would be an ancestor of the proposed lock,
  117. // which conflicts if the ancestor has infinite depth.
  118. if n[len(n)-1] == 'i' {
  119. return false
  120. }
  121. }
  122. }
  123. return true
  124. }
  125. var check func(int, string)
  126. check = func(recursion int, name string) {
  127. for _, zeroDepth := range []bool{false, true} {
  128. got := m.canCreate(name, zeroDepth)
  129. want := wantCanCreate(name, zeroDepth)
  130. if got != want {
  131. t.Errorf("canCreate name=%q zeroDepth=%t: got %t, want %t", name, zeroDepth, got, want)
  132. }
  133. }
  134. if recursion == 6 {
  135. return
  136. }
  137. if name != "/" {
  138. name += "/"
  139. }
  140. for _, c := range "_iz" {
  141. check(recursion+1, name+string(c))
  142. }
  143. }
  144. check(0, "/")
  145. }
  146. func TestMemLSLookup(t *testing.T) {
  147. now := time.Unix(0, 0)
  148. m := NewMemLS().(*memLS)
  149. badToken := m.nextToken()
  150. t.Logf("badToken=%q", badToken)
  151. for _, name := range lockTestNames {
  152. token, err := m.Create(now, LockDetails{
  153. Root: name,
  154. Duration: infiniteTimeout,
  155. ZeroDepth: lockTestZeroDepth(name),
  156. })
  157. if err != nil {
  158. t.Fatalf("creating lock for %q: %v", name, err)
  159. }
  160. t.Logf("%-15q -> node=%p token=%q", name, m.byName[name], token)
  161. }
  162. baseNames := append([]string{"/a", "/b/c"}, lockTestNames...)
  163. for _, baseName := range baseNames {
  164. for _, suffix := range []string{"", "/0", "/1/2/3"} {
  165. name := baseName + suffix
  166. goodToken := ""
  167. base := m.byName[baseName]
  168. if base != nil && (suffix == "" || !lockTestZeroDepth(baseName)) {
  169. goodToken = base.token
  170. }
  171. for _, token := range []string{badToken, goodToken} {
  172. if token == "" {
  173. continue
  174. }
  175. got := m.lookup(name, Condition{Token: token})
  176. want := base
  177. if token == badToken {
  178. want = nil
  179. }
  180. if got != want {
  181. t.Errorf("name=%-20qtoken=%q (bad=%t): got %p, want %p",
  182. name, token, token == badToken, got, want)
  183. }
  184. }
  185. }
  186. }
  187. }
  188. func TestMemLSConfirm(t *testing.T) {
  189. now := time.Unix(0, 0)
  190. m := NewMemLS().(*memLS)
  191. alice, err := m.Create(now, LockDetails{
  192. Root: "/alice",
  193. Duration: infiniteTimeout,
  194. ZeroDepth: false,
  195. })
  196. tweedle, err := m.Create(now, LockDetails{
  197. Root: "/tweedle",
  198. Duration: infiniteTimeout,
  199. ZeroDepth: false,
  200. })
  201. if err != nil {
  202. t.Fatalf("Create: %v", err)
  203. }
  204. if err := m.consistent(); err != nil {
  205. t.Fatalf("Create: inconsistent state: %v", err)
  206. }
  207. // Test a mismatch between name and condition.
  208. _, err = m.Confirm(now, "/tweedle/dee", "", Condition{Token: alice})
  209. if err != ErrConfirmationFailed {
  210. t.Fatalf("Confirm (mismatch): got %v, want ErrConfirmationFailed", err)
  211. }
  212. if err := m.consistent(); err != nil {
  213. t.Fatalf("Confirm (mismatch): inconsistent state: %v", err)
  214. }
  215. // Test two names (that fall under the same lock) in the one Confirm call.
  216. release, err := m.Confirm(now, "/tweedle/dee", "/tweedle/dum", Condition{Token: tweedle})
  217. if err != nil {
  218. t.Fatalf("Confirm (twins): %v", err)
  219. }
  220. if err := m.consistent(); err != nil {
  221. t.Fatalf("Confirm (twins): inconsistent state: %v", err)
  222. }
  223. release()
  224. if err := m.consistent(); err != nil {
  225. t.Fatalf("release (twins): inconsistent state: %v", err)
  226. }
  227. // Test the same two names in overlapping Confirm / release calls.
  228. releaseDee, err := m.Confirm(now, "/tweedle/dee", "", Condition{Token: tweedle})
  229. if err != nil {
  230. t.Fatalf("Confirm (sequence #0): %v", err)
  231. }
  232. if err := m.consistent(); err != nil {
  233. t.Fatalf("Confirm (sequence #0): inconsistent state: %v", err)
  234. }
  235. _, err = m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle})
  236. if err != ErrConfirmationFailed {
  237. t.Fatalf("Confirm (sequence #1): got %v, want ErrConfirmationFailed", err)
  238. }
  239. if err := m.consistent(); err != nil {
  240. t.Fatalf("Confirm (sequence #1): inconsistent state: %v", err)
  241. }
  242. releaseDee()
  243. if err := m.consistent(); err != nil {
  244. t.Fatalf("release (sequence #2): inconsistent state: %v", err)
  245. }
  246. releaseDum, err := m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle})
  247. if err != nil {
  248. t.Fatalf("Confirm (sequence #3): %v", err)
  249. }
  250. if err := m.consistent(); err != nil {
  251. t.Fatalf("Confirm (sequence #3): inconsistent state: %v", err)
  252. }
  253. // Test that you can't unlock a held lock.
  254. err = m.Unlock(now, tweedle)
  255. if err != ErrLocked {
  256. t.Fatalf("Unlock (sequence #4): got %v, want ErrLocked", err)
  257. }
  258. releaseDum()
  259. if err := m.consistent(); err != nil {
  260. t.Fatalf("release (sequence #5): inconsistent state: %v", err)
  261. }
  262. err = m.Unlock(now, tweedle)
  263. if err != nil {
  264. t.Fatalf("Unlock (sequence #6): %v", err)
  265. }
  266. if err := m.consistent(); err != nil {
  267. t.Fatalf("Unlock (sequence #6): inconsistent state: %v", err)
  268. }
  269. }
  270. func TestMemLSNonCanonicalRoot(t *testing.T) {
  271. now := time.Unix(0, 0)
  272. m := NewMemLS().(*memLS)
  273. token, err := m.Create(now, LockDetails{
  274. Root: "/foo/./bar//",
  275. Duration: 1 * time.Second,
  276. })
  277. if err != nil {
  278. t.Fatalf("Create: %v", err)
  279. }
  280. if err := m.consistent(); err != nil {
  281. t.Fatalf("Create: inconsistent state: %v", err)
  282. }
  283. if err := m.Unlock(now, token); err != nil {
  284. t.Fatalf("Unlock: %v", err)
  285. }
  286. if err := m.consistent(); err != nil {
  287. t.Fatalf("Unlock: inconsistent state: %v", err)
  288. }
  289. }
  290. func TestMemLSExpiry(t *testing.T) {
  291. m := NewMemLS().(*memLS)
  292. testCases := []string{
  293. "setNow 0",
  294. "create /a.5",
  295. "want /a.5",
  296. "create /c.6",
  297. "want /a.5 /c.6",
  298. "create /a/b.7",
  299. "want /a.5 /a/b.7 /c.6",
  300. "setNow 4",
  301. "want /a.5 /a/b.7 /c.6",
  302. "setNow 5",
  303. "want /a/b.7 /c.6",
  304. "setNow 6",
  305. "want /a/b.7",
  306. "setNow 7",
  307. "want ",
  308. "setNow 8",
  309. "want ",
  310. "create /a.12",
  311. "create /b.13",
  312. "create /c.15",
  313. "create /a/d.16",
  314. "want /a.12 /a/d.16 /b.13 /c.15",
  315. "refresh /a.14",
  316. "want /a.14 /a/d.16 /b.13 /c.15",
  317. "setNow 12",
  318. "want /a.14 /a/d.16 /b.13 /c.15",
  319. "setNow 13",
  320. "want /a.14 /a/d.16 /c.15",
  321. "setNow 14",
  322. "want /a/d.16 /c.15",
  323. "refresh /a/d.20",
  324. "refresh /c.20",
  325. "want /a/d.20 /c.20",
  326. "setNow 20",
  327. "want ",
  328. }
  329. tokens := map[string]string{}
  330. zTime := time.Unix(0, 0)
  331. now := zTime
  332. for i, tc := range testCases {
  333. j := strings.IndexByte(tc, ' ')
  334. if j < 0 {
  335. t.Fatalf("test case #%d %q: invalid command", i, tc)
  336. }
  337. op, arg := tc[:j], tc[j+1:]
  338. switch op {
  339. default:
  340. t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op)
  341. case "create", "refresh":
  342. parts := strings.Split(arg, ".")
  343. if len(parts) != 2 {
  344. t.Fatalf("test case #%d %q: invalid create", i, tc)
  345. }
  346. root := parts[0]
  347. d, err := strconv.Atoi(parts[1])
  348. if err != nil {
  349. t.Fatalf("test case #%d %q: invalid duration", i, tc)
  350. }
  351. dur := time.Unix(0, 0).Add(time.Duration(d) * time.Second).Sub(now)
  352. switch op {
  353. case "create":
  354. token, err := m.Create(now, LockDetails{
  355. Root: root,
  356. Duration: dur,
  357. ZeroDepth: true,
  358. })
  359. if err != nil {
  360. t.Fatalf("test case #%d %q: Create: %v", i, tc, err)
  361. }
  362. tokens[root] = token
  363. case "refresh":
  364. token := tokens[root]
  365. if token == "" {
  366. t.Fatalf("test case #%d %q: no token for %q", i, tc, root)
  367. }
  368. got, err := m.Refresh(now, token, dur)
  369. if err != nil {
  370. t.Fatalf("test case #%d %q: Refresh: %v", i, tc, err)
  371. }
  372. want := LockDetails{
  373. Root: root,
  374. Duration: dur,
  375. ZeroDepth: true,
  376. }
  377. if got != want {
  378. t.Fatalf("test case #%d %q:\ngot %v\nwant %v", i, tc, got, want)
  379. }
  380. }
  381. case "setNow":
  382. d, err := strconv.Atoi(arg)
  383. if err != nil {
  384. t.Fatalf("test case #%d %q: invalid duration", i, tc)
  385. }
  386. now = time.Unix(0, 0).Add(time.Duration(d) * time.Second)
  387. case "want":
  388. m.mu.Lock()
  389. m.collectExpiredNodes(now)
  390. got := make([]string, 0, len(m.byToken))
  391. for _, n := range m.byToken {
  392. got = append(got, fmt.Sprintf("%s.%d",
  393. n.details.Root, n.expiry.Sub(zTime)/time.Second))
  394. }
  395. m.mu.Unlock()
  396. sort.Strings(got)
  397. want := []string{}
  398. if arg != "" {
  399. want = strings.Split(arg, " ")
  400. }
  401. if !reflect.DeepEqual(got, want) {
  402. t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, want)
  403. }
  404. }
  405. if err := m.consistent(); err != nil {
  406. t.Fatalf("test case #%d %q: inconsistent state: %v", i, tc, err)
  407. }
  408. }
  409. }
  410. func TestMemLS(t *testing.T) {
  411. now := time.Unix(0, 0)
  412. m := NewMemLS().(*memLS)
  413. rng := rand.New(rand.NewSource(0))
  414. tokens := map[string]string{}
  415. nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0
  416. const N = 2000
  417. for i := 0; i < N; i++ {
  418. name := lockTestNames[rng.Intn(len(lockTestNames))]
  419. duration := lockTestDurations[rng.Intn(len(lockTestDurations))]
  420. confirmed, unlocked := false, false
  421. // If the name was already locked, we randomly confirm/release, refresh
  422. // or unlock it. Otherwise, we create a lock.
  423. token := tokens[name]
  424. if token != "" {
  425. switch rng.Intn(3) {
  426. case 0:
  427. confirmed = true
  428. nConfirm++
  429. release, err := m.Confirm(now, name, "", Condition{Token: token})
  430. if err != nil {
  431. t.Fatalf("iteration #%d: Confirm %q: %v", i, name, err)
  432. }
  433. if err := m.consistent(); err != nil {
  434. t.Fatalf("iteration #%d: inconsistent state: %v", i, err)
  435. }
  436. release()
  437. case 1:
  438. nRefresh++
  439. if _, err := m.Refresh(now, token, duration); err != nil {
  440. t.Fatalf("iteration #%d: Refresh %q: %v", i, name, err)
  441. }
  442. case 2:
  443. unlocked = true
  444. nUnlock++
  445. if err := m.Unlock(now, token); err != nil {
  446. t.Fatalf("iteration #%d: Unlock %q: %v", i, name, err)
  447. }
  448. }
  449. } else {
  450. nCreate++
  451. var err error
  452. token, err = m.Create(now, LockDetails{
  453. Root: name,
  454. Duration: duration,
  455. ZeroDepth: lockTestZeroDepth(name),
  456. })
  457. if err != nil {
  458. t.Fatalf("iteration #%d: Create %q: %v", i, name, err)
  459. }
  460. }
  461. if !confirmed {
  462. if duration == 0 || unlocked {
  463. // A zero-duration lock should expire immediately and is
  464. // effectively equivalent to being unlocked.
  465. tokens[name] = ""
  466. } else {
  467. tokens[name] = token
  468. }
  469. }
  470. if err := m.consistent(); err != nil {
  471. t.Fatalf("iteration #%d: inconsistent state: %v", i, err)
  472. }
  473. }
  474. if nConfirm < N/10 {
  475. t.Fatalf("too few Confirm calls: got %d, want >= %d", nConfirm, N/10)
  476. }
  477. if nCreate < N/10 {
  478. t.Fatalf("too few Create calls: got %d, want >= %d", nCreate, N/10)
  479. }
  480. if nRefresh < N/10 {
  481. t.Fatalf("too few Refresh calls: got %d, want >= %d", nRefresh, N/10)
  482. }
  483. if nUnlock < N/10 {
  484. t.Fatalf("too few Unlock calls: got %d, want >= %d", nUnlock, N/10)
  485. }
  486. }
  487. func (m *memLS) consistent() error {
  488. m.mu.Lock()
  489. defer m.mu.Unlock()
  490. // If m.byName is non-empty, then it must contain an entry for the root "/",
  491. // and its refCount should equal the number of locked nodes.
  492. if len(m.byName) > 0 {
  493. n := m.byName["/"]
  494. if n == nil {
  495. return fmt.Errorf(`non-empty m.byName does not contain the root "/"`)
  496. }
  497. if n.refCount != len(m.byToken) {
  498. return fmt.Errorf("root node refCount=%d, differs from len(m.byToken)=%d", n.refCount, len(m.byToken))
  499. }
  500. }
  501. for name, n := range m.byName {
  502. // The map keys should be consistent with the node's copy of the key.
  503. if n.details.Root != name {
  504. return fmt.Errorf("node name %q != byName map key %q", n.details.Root, name)
  505. }
  506. // A name must be clean, and start with a "/".
  507. if len(name) == 0 || name[0] != '/' {
  508. return fmt.Errorf(`node name %q does not start with "/"`, name)
  509. }
  510. if name != path.Clean(name) {
  511. return fmt.Errorf(`node name %q is not clean`, name)
  512. }
  513. // A node's refCount should be positive.
  514. if n.refCount <= 0 {
  515. return fmt.Errorf("non-positive refCount for node at name %q", name)
  516. }
  517. // A node's refCount should be the number of self-or-descendents that
  518. // are locked (i.e. have a non-empty token).
  519. var list []string
  520. for name0, n0 := range m.byName {
  521. // All of lockTestNames' name fragments are one byte long: '_', 'i' or 'z',
  522. // so strings.HasPrefix is equivalent to self-or-descendent name match.
  523. // We don't have to worry about "/foo/bar" being a false positive match
  524. // for "/foo/b".
  525. if strings.HasPrefix(name0, name) && n0.token != "" {
  526. list = append(list, name0)
  527. }
  528. }
  529. if n.refCount != len(list) {
  530. sort.Strings(list)
  531. return fmt.Errorf("node at name %q has refCount %d but locked self-or-descendents are %q (len=%d)",
  532. name, n.refCount, list, len(list))
  533. }
  534. // A node n is in m.byToken if it has a non-empty token.
  535. if n.token != "" {
  536. if _, ok := m.byToken[n.token]; !ok {
  537. return fmt.Errorf("node at name %q has token %q but not in m.byToken", name, n.token)
  538. }
  539. }
  540. // A node n is in m.byExpiry if it has a non-negative byExpiryIndex.
  541. if n.byExpiryIndex >= 0 {
  542. if n.byExpiryIndex >= len(m.byExpiry) {
  543. return fmt.Errorf("node at name %q has byExpiryIndex %d but m.byExpiry has length %d", name, n.byExpiryIndex, len(m.byExpiry))
  544. }
  545. if n != m.byExpiry[n.byExpiryIndex] {
  546. return fmt.Errorf("node at name %q has byExpiryIndex %d but that indexes a different node", name, n.byExpiryIndex)
  547. }
  548. }
  549. }
  550. for token, n := range m.byToken {
  551. // The map keys should be consistent with the node's copy of the key.
  552. if n.token != token {
  553. return fmt.Errorf("node token %q != byToken map key %q", n.token, token)
  554. }
  555. // Every node in m.byToken is in m.byName.
  556. if _, ok := m.byName[n.details.Root]; !ok {
  557. return fmt.Errorf("node at name %q in m.byToken but not in m.byName", n.details.Root)
  558. }
  559. }
  560. for i, n := range m.byExpiry {
  561. // The slice indices should be consistent with the node's copy of the index.
  562. if n.byExpiryIndex != i {
  563. return fmt.Errorf("node byExpiryIndex %d != byExpiry slice index %d", n.byExpiryIndex, i)
  564. }
  565. // Every node in m.byExpiry is in m.byName.
  566. if _, ok := m.byName[n.details.Root]; !ok {
  567. return fmt.Errorf("node at name %q in m.byExpiry but not in m.byName", n.details.Root)
  568. }
  569. // No node in m.byExpiry should be held.
  570. if n.held {
  571. return fmt.Errorf("node at name %q in m.byExpiry is held", n.details.Root)
  572. }
  573. }
  574. return nil
  575. }
  576. func TestParseTimeout(t *testing.T) {
  577. testCases := []struct {
  578. s string
  579. want time.Duration
  580. wantErr error
  581. }{{
  582. "",
  583. infiniteTimeout,
  584. nil,
  585. }, {
  586. "Infinite",
  587. infiniteTimeout,
  588. nil,
  589. }, {
  590. "Infinitesimal",
  591. 0,
  592. errInvalidTimeout,
  593. }, {
  594. "infinite",
  595. 0,
  596. errInvalidTimeout,
  597. }, {
  598. "Second-0",
  599. 0 * time.Second,
  600. nil,
  601. }, {
  602. "Second-123",
  603. 123 * time.Second,
  604. nil,
  605. }, {
  606. " Second-456 ",
  607. 456 * time.Second,
  608. nil,
  609. }, {
  610. "Second-4100000000",
  611. 4100000000 * time.Second,
  612. nil,
  613. }, {
  614. "junk",
  615. 0,
  616. errInvalidTimeout,
  617. }, {
  618. "Second-",
  619. 0,
  620. errInvalidTimeout,
  621. }, {
  622. "Second--1",
  623. 0,
  624. errInvalidTimeout,
  625. }, {
  626. "Second--123",
  627. 0,
  628. errInvalidTimeout,
  629. }, {
  630. "Second-+123",
  631. 0,
  632. errInvalidTimeout,
  633. }, {
  634. "Second-0x123",
  635. 0,
  636. errInvalidTimeout,
  637. }, {
  638. "second-123",
  639. 0,
  640. errInvalidTimeout,
  641. }, {
  642. "Second-4294967295",
  643. 4294967295 * time.Second,
  644. nil,
  645. }, {
  646. // Section 10.7 says that "The timeout value for TimeType "Second"
  647. // must not be greater than 2^32-1."
  648. "Second-4294967296",
  649. 0,
  650. errInvalidTimeout,
  651. }, {
  652. // This test case comes from section 9.10.9 of the spec. It says,
  653. //
  654. // "In this request, the client has specified that it desires an
  655. // infinite-length lock, if available, otherwise a timeout of 4.1
  656. // billion seconds, if available."
  657. //
  658. // The Go WebDAV package always supports infinite length locks,
  659. // and ignores the fallback after the comma.
  660. "Infinite, Second-4100000000",
  661. infiniteTimeout,
  662. nil,
  663. }}
  664. for _, tc := range testCases {
  665. got, gotErr := parseTimeout(tc.s)
  666. if got != tc.want || gotErr != tc.wantErr {
  667. t.Errorf("parsing %q:\ngot %v, %v\nwant %v, %v", tc.s, got, gotErr, tc.want, tc.wantErr)
  668. }
  669. }
  670. }