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.

603 lines
18 KiB

  1. package inspect_test
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "os"
  7. "strings"
  8. "sync"
  9. "testing"
  10. "time"
  11. "github.com/fortytw2/leaktest"
  12. "github.com/stretchr/testify/mock"
  13. "github.com/stretchr/testify/require"
  14. abcitypes "github.com/tendermint/tendermint/abci/types"
  15. "github.com/tendermint/tendermint/config"
  16. "github.com/tendermint/tendermint/internal/inspect"
  17. "github.com/tendermint/tendermint/internal/state/indexer"
  18. indexermocks "github.com/tendermint/tendermint/internal/state/indexer/mocks"
  19. statemocks "github.com/tendermint/tendermint/internal/state/mocks"
  20. "github.com/tendermint/tendermint/libs/log"
  21. "github.com/tendermint/tendermint/libs/pubsub/query"
  22. "github.com/tendermint/tendermint/proto/tendermint/state"
  23. httpclient "github.com/tendermint/tendermint/rpc/client/http"
  24. "github.com/tendermint/tendermint/types"
  25. )
  26. func TestInspectConstructor(t *testing.T) {
  27. cfg, err := config.ResetTestRoot("test")
  28. require.NoError(t, err)
  29. testLogger := log.TestingLogger()
  30. t.Cleanup(leaktest.Check(t))
  31. defer func() { _ = os.RemoveAll(cfg.RootDir) }()
  32. t.Run("from config", func(t *testing.T) {
  33. logger := testLogger.With(t.Name())
  34. d, err := inspect.NewFromConfig(logger, cfg)
  35. require.NoError(t, err)
  36. require.NotNil(t, d)
  37. })
  38. }
  39. func TestInspectRun(t *testing.T) {
  40. cfg, err := config.ResetTestRoot("test")
  41. require.NoError(t, err)
  42. testLogger := log.TestingLogger()
  43. t.Cleanup(leaktest.Check(t))
  44. defer func() { _ = os.RemoveAll(cfg.RootDir) }()
  45. t.Run("from config", func(t *testing.T) {
  46. logger := testLogger.With(t.Name())
  47. d, err := inspect.NewFromConfig(logger, cfg)
  48. require.NoError(t, err)
  49. ctx, cancel := context.WithCancel(context.Background())
  50. stoppedWG := &sync.WaitGroup{}
  51. stoppedWG.Add(1)
  52. go func() {
  53. require.NoError(t, d.Run(ctx))
  54. stoppedWG.Done()
  55. }()
  56. cancel()
  57. stoppedWG.Wait()
  58. })
  59. }
  60. func TestBlock(t *testing.T) {
  61. testHeight := int64(1)
  62. testBlock := new(types.Block)
  63. testBlock.Header.Height = testHeight
  64. testBlock.Header.LastCommitHash = []byte("test hash")
  65. stateStoreMock := &statemocks.Store{}
  66. blockStoreMock := &statemocks.BlockStore{}
  67. blockStoreMock.On("Height").Return(testHeight)
  68. blockStoreMock.On("Base").Return(int64(0))
  69. blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{})
  70. blockStoreMock.On("LoadBlock", testHeight).Return(testBlock)
  71. eventSinkMock := &indexermocks.EventSink{}
  72. eventSinkMock.On("Stop").Return(nil)
  73. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  74. rpcConfig := config.TestRPCConfig()
  75. l := log.TestingLogger()
  76. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  77. ctx, cancel := context.WithCancel(context.Background())
  78. wg := &sync.WaitGroup{}
  79. wg.Add(1)
  80. startedWG := &sync.WaitGroup{}
  81. startedWG.Add(1)
  82. go func() {
  83. startedWG.Done()
  84. defer wg.Done()
  85. require.NoError(t, d.Run(ctx))
  86. }()
  87. // FIXME: used to induce context switch.
  88. // Determine more deterministic method for prompting a context switch
  89. startedWG.Wait()
  90. requireConnect(t, rpcConfig.ListenAddress, 20)
  91. cli, err := httpclient.New(rpcConfig.ListenAddress)
  92. require.NoError(t, err)
  93. resultBlock, err := cli.Block(ctx, &testHeight)
  94. require.NoError(t, err)
  95. require.Equal(t, testBlock.Height, resultBlock.Block.Height)
  96. require.Equal(t, testBlock.LastCommitHash, resultBlock.Block.LastCommitHash)
  97. cancel()
  98. wg.Wait()
  99. blockStoreMock.AssertExpectations(t)
  100. stateStoreMock.AssertExpectations(t)
  101. }
  102. func TestTxSearch(t *testing.T) {
  103. testHash := []byte("test")
  104. testTx := []byte("tx")
  105. testQuery := fmt.Sprintf("tx.hash='%s'", string(testHash))
  106. testTxResult := &abcitypes.TxResult{
  107. Height: 1,
  108. Index: 100,
  109. Tx: testTx,
  110. }
  111. stateStoreMock := &statemocks.Store{}
  112. blockStoreMock := &statemocks.BlockStore{}
  113. eventSinkMock := &indexermocks.EventSink{}
  114. eventSinkMock.On("Stop").Return(nil)
  115. eventSinkMock.On("Type").Return(indexer.KV)
  116. eventSinkMock.On("SearchTxEvents", mock.Anything,
  117. mock.MatchedBy(func(q *query.Query) bool { return testQuery == q.String() })).
  118. Return([]*abcitypes.TxResult{testTxResult}, nil)
  119. rpcConfig := config.TestRPCConfig()
  120. l := log.TestingLogger()
  121. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  122. ctx, cancel := context.WithCancel(context.Background())
  123. wg := &sync.WaitGroup{}
  124. wg.Add(1)
  125. startedWG := &sync.WaitGroup{}
  126. startedWG.Add(1)
  127. go func() {
  128. startedWG.Done()
  129. defer wg.Done()
  130. require.NoError(t, d.Run(ctx))
  131. }()
  132. // FIXME: used to induce context switch.
  133. // Determine more deterministic method for prompting a context switch
  134. startedWG.Wait()
  135. requireConnect(t, rpcConfig.ListenAddress, 20)
  136. cli, err := httpclient.New(rpcConfig.ListenAddress)
  137. require.NoError(t, err)
  138. var page = 1
  139. resultTxSearch, err := cli.TxSearch(ctx, testQuery, false, &page, &page, "")
  140. require.NoError(t, err)
  141. require.Len(t, resultTxSearch.Txs, 1)
  142. require.Equal(t, types.Tx(testTx), resultTxSearch.Txs[0].Tx)
  143. cancel()
  144. wg.Wait()
  145. eventSinkMock.AssertExpectations(t)
  146. stateStoreMock.AssertExpectations(t)
  147. blockStoreMock.AssertExpectations(t)
  148. }
  149. func TestTx(t *testing.T) {
  150. testHash := []byte("test")
  151. testTx := []byte("tx")
  152. stateStoreMock := &statemocks.Store{}
  153. blockStoreMock := &statemocks.BlockStore{}
  154. eventSinkMock := &indexermocks.EventSink{}
  155. eventSinkMock.On("Stop").Return(nil)
  156. eventSinkMock.On("Type").Return(indexer.KV)
  157. eventSinkMock.On("GetTxByHash", testHash).Return(&abcitypes.TxResult{
  158. Tx: testTx,
  159. }, nil)
  160. rpcConfig := config.TestRPCConfig()
  161. l := log.TestingLogger()
  162. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  163. ctx, cancel := context.WithCancel(context.Background())
  164. wg := &sync.WaitGroup{}
  165. wg.Add(1)
  166. startedWG := &sync.WaitGroup{}
  167. startedWG.Add(1)
  168. go func() {
  169. startedWG.Done()
  170. defer wg.Done()
  171. require.NoError(t, d.Run(ctx))
  172. }()
  173. // FIXME: used to induce context switch.
  174. // Determine more deterministic method for prompting a context switch
  175. startedWG.Wait()
  176. requireConnect(t, rpcConfig.ListenAddress, 20)
  177. cli, err := httpclient.New(rpcConfig.ListenAddress)
  178. require.NoError(t, err)
  179. res, err := cli.Tx(ctx, testHash, false)
  180. require.NoError(t, err)
  181. require.Equal(t, types.Tx(testTx), res.Tx)
  182. cancel()
  183. wg.Wait()
  184. eventSinkMock.AssertExpectations(t)
  185. stateStoreMock.AssertExpectations(t)
  186. blockStoreMock.AssertExpectations(t)
  187. }
  188. func TestConsensusParams(t *testing.T) {
  189. testHeight := int64(1)
  190. testMaxGas := int64(55)
  191. stateStoreMock := &statemocks.Store{}
  192. blockStoreMock := &statemocks.BlockStore{}
  193. blockStoreMock.On("Height").Return(testHeight)
  194. blockStoreMock.On("Base").Return(int64(0))
  195. stateStoreMock.On("LoadConsensusParams", testHeight).Return(types.ConsensusParams{
  196. Block: types.BlockParams{
  197. MaxGas: testMaxGas,
  198. },
  199. }, nil)
  200. eventSinkMock := &indexermocks.EventSink{}
  201. eventSinkMock.On("Stop").Return(nil)
  202. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  203. rpcConfig := config.TestRPCConfig()
  204. l := log.TestingLogger()
  205. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  206. ctx, cancel := context.WithCancel(context.Background())
  207. wg := &sync.WaitGroup{}
  208. wg.Add(1)
  209. startedWG := &sync.WaitGroup{}
  210. startedWG.Add(1)
  211. go func() {
  212. startedWG.Done()
  213. defer wg.Done()
  214. require.NoError(t, d.Run(ctx))
  215. }()
  216. // FIXME: used to induce context switch.
  217. // Determine more deterministic method for prompting a context switch
  218. startedWG.Wait()
  219. requireConnect(t, rpcConfig.ListenAddress, 20)
  220. cli, err := httpclient.New(rpcConfig.ListenAddress)
  221. require.NoError(t, err)
  222. params, err := cli.ConsensusParams(ctx, &testHeight)
  223. require.NoError(t, err)
  224. require.Equal(t, params.ConsensusParams.Block.MaxGas, testMaxGas)
  225. cancel()
  226. wg.Wait()
  227. blockStoreMock.AssertExpectations(t)
  228. stateStoreMock.AssertExpectations(t)
  229. }
  230. func TestBlockResults(t *testing.T) {
  231. testHeight := int64(1)
  232. testGasUsed := int64(100)
  233. stateStoreMock := &statemocks.Store{}
  234. // tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
  235. stateStoreMock.On("LoadABCIResponses", testHeight).Return(&state.ABCIResponses{
  236. DeliverTxs: []*abcitypes.ResponseDeliverTx{
  237. {
  238. GasUsed: testGasUsed,
  239. },
  240. },
  241. EndBlock: &abcitypes.ResponseEndBlock{},
  242. BeginBlock: &abcitypes.ResponseBeginBlock{},
  243. }, nil)
  244. blockStoreMock := &statemocks.BlockStore{}
  245. blockStoreMock.On("Base").Return(int64(0))
  246. blockStoreMock.On("Height").Return(testHeight)
  247. eventSinkMock := &indexermocks.EventSink{}
  248. eventSinkMock.On("Stop").Return(nil)
  249. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  250. rpcConfig := config.TestRPCConfig()
  251. l := log.TestingLogger()
  252. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  253. ctx, cancel := context.WithCancel(context.Background())
  254. wg := &sync.WaitGroup{}
  255. wg.Add(1)
  256. startedWG := &sync.WaitGroup{}
  257. startedWG.Add(1)
  258. go func() {
  259. startedWG.Done()
  260. defer wg.Done()
  261. require.NoError(t, d.Run(ctx))
  262. }()
  263. // FIXME: used to induce context switch.
  264. // Determine more deterministic method for prompting a context switch
  265. startedWG.Wait()
  266. requireConnect(t, rpcConfig.ListenAddress, 20)
  267. cli, err := httpclient.New(rpcConfig.ListenAddress)
  268. require.NoError(t, err)
  269. res, err := cli.BlockResults(ctx, &testHeight)
  270. require.NoError(t, err)
  271. require.Equal(t, res.TotalGasUsed, testGasUsed)
  272. cancel()
  273. wg.Wait()
  274. blockStoreMock.AssertExpectations(t)
  275. stateStoreMock.AssertExpectations(t)
  276. }
  277. func TestCommit(t *testing.T) {
  278. testHeight := int64(1)
  279. testRound := int32(101)
  280. stateStoreMock := &statemocks.Store{}
  281. blockStoreMock := &statemocks.BlockStore{}
  282. blockStoreMock.On("Base").Return(int64(0))
  283. blockStoreMock.On("Height").Return(testHeight)
  284. blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{}, nil)
  285. blockStoreMock.On("LoadSeenCommit").Return(&types.Commit{
  286. Height: testHeight,
  287. Round: testRound,
  288. }, nil)
  289. eventSinkMock := &indexermocks.EventSink{}
  290. eventSinkMock.On("Stop").Return(nil)
  291. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  292. rpcConfig := config.TestRPCConfig()
  293. l := log.TestingLogger()
  294. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  295. ctx, cancel := context.WithCancel(context.Background())
  296. wg := &sync.WaitGroup{}
  297. wg.Add(1)
  298. startedWG := &sync.WaitGroup{}
  299. startedWG.Add(1)
  300. go func() {
  301. startedWG.Done()
  302. defer wg.Done()
  303. require.NoError(t, d.Run(ctx))
  304. }()
  305. // FIXME: used to induce context switch.
  306. // Determine more deterministic method for prompting a context switch
  307. startedWG.Wait()
  308. requireConnect(t, rpcConfig.ListenAddress, 20)
  309. cli, err := httpclient.New(rpcConfig.ListenAddress)
  310. require.NoError(t, err)
  311. res, err := cli.Commit(ctx, &testHeight)
  312. require.NoError(t, err)
  313. require.NotNil(t, res)
  314. require.Equal(t, res.SignedHeader.Commit.Round, testRound)
  315. cancel()
  316. wg.Wait()
  317. blockStoreMock.AssertExpectations(t)
  318. stateStoreMock.AssertExpectations(t)
  319. }
  320. func TestBlockByHash(t *testing.T) {
  321. testHeight := int64(1)
  322. testHash := []byte("test hash")
  323. testBlock := new(types.Block)
  324. testBlock.Header.Height = testHeight
  325. testBlock.Header.LastCommitHash = testHash
  326. stateStoreMock := &statemocks.Store{}
  327. blockStoreMock := &statemocks.BlockStore{}
  328. blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{
  329. BlockID: types.BlockID{
  330. Hash: testHash,
  331. },
  332. Header: types.Header{
  333. Height: testHeight,
  334. },
  335. }, nil)
  336. blockStoreMock.On("LoadBlockByHash", testHash).Return(testBlock, nil)
  337. eventSinkMock := &indexermocks.EventSink{}
  338. eventSinkMock.On("Stop").Return(nil)
  339. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  340. rpcConfig := config.TestRPCConfig()
  341. l := log.TestingLogger()
  342. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  343. ctx, cancel := context.WithCancel(context.Background())
  344. wg := &sync.WaitGroup{}
  345. wg.Add(1)
  346. startedWG := &sync.WaitGroup{}
  347. startedWG.Add(1)
  348. go func() {
  349. startedWG.Done()
  350. defer wg.Done()
  351. require.NoError(t, d.Run(ctx))
  352. }()
  353. // FIXME: used to induce context switch.
  354. // Determine more deterministic method for prompting a context switch
  355. startedWG.Wait()
  356. requireConnect(t, rpcConfig.ListenAddress, 20)
  357. cli, err := httpclient.New(rpcConfig.ListenAddress)
  358. require.NoError(t, err)
  359. res, err := cli.BlockByHash(ctx, testHash)
  360. require.NoError(t, err)
  361. require.NotNil(t, res)
  362. require.Equal(t, []byte(res.BlockID.Hash), testHash)
  363. cancel()
  364. wg.Wait()
  365. blockStoreMock.AssertExpectations(t)
  366. stateStoreMock.AssertExpectations(t)
  367. }
  368. func TestBlockchain(t *testing.T) {
  369. testHeight := int64(1)
  370. testBlock := new(types.Block)
  371. testBlockHash := []byte("test hash")
  372. testBlock.Header.Height = testHeight
  373. testBlock.Header.LastCommitHash = testBlockHash
  374. stateStoreMock := &statemocks.Store{}
  375. blockStoreMock := &statemocks.BlockStore{}
  376. blockStoreMock.On("Height").Return(testHeight)
  377. blockStoreMock.On("Base").Return(int64(0))
  378. blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{
  379. BlockID: types.BlockID{
  380. Hash: testBlockHash,
  381. },
  382. })
  383. eventSinkMock := &indexermocks.EventSink{}
  384. eventSinkMock.On("Stop").Return(nil)
  385. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  386. rpcConfig := config.TestRPCConfig()
  387. l := log.TestingLogger()
  388. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  389. ctx, cancel := context.WithCancel(context.Background())
  390. wg := &sync.WaitGroup{}
  391. wg.Add(1)
  392. startedWG := &sync.WaitGroup{}
  393. startedWG.Add(1)
  394. go func() {
  395. startedWG.Done()
  396. defer wg.Done()
  397. require.NoError(t, d.Run(ctx))
  398. }()
  399. // FIXME: used to induce context switch.
  400. // Determine more deterministic method for prompting a context switch
  401. startedWG.Wait()
  402. requireConnect(t, rpcConfig.ListenAddress, 20)
  403. cli, err := httpclient.New(rpcConfig.ListenAddress)
  404. require.NoError(t, err)
  405. res, err := cli.BlockchainInfo(ctx, 0, 100)
  406. require.NoError(t, err)
  407. require.NotNil(t, res)
  408. require.Equal(t, testBlockHash, []byte(res.BlockMetas[0].BlockID.Hash))
  409. cancel()
  410. wg.Wait()
  411. blockStoreMock.AssertExpectations(t)
  412. stateStoreMock.AssertExpectations(t)
  413. }
  414. func TestValidators(t *testing.T) {
  415. testHeight := int64(1)
  416. testVotingPower := int64(100)
  417. testValidators := types.ValidatorSet{
  418. Validators: []*types.Validator{
  419. {
  420. VotingPower: testVotingPower,
  421. },
  422. },
  423. }
  424. stateStoreMock := &statemocks.Store{}
  425. stateStoreMock.On("LoadValidators", testHeight).Return(&testValidators, nil)
  426. blockStoreMock := &statemocks.BlockStore{}
  427. blockStoreMock.On("Height").Return(testHeight)
  428. blockStoreMock.On("Base").Return(int64(0))
  429. eventSinkMock := &indexermocks.EventSink{}
  430. eventSinkMock.On("Stop").Return(nil)
  431. eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock"))
  432. rpcConfig := config.TestRPCConfig()
  433. l := log.TestingLogger()
  434. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  435. ctx, cancel := context.WithCancel(context.Background())
  436. wg := &sync.WaitGroup{}
  437. wg.Add(1)
  438. startedWG := &sync.WaitGroup{}
  439. startedWG.Add(1)
  440. go func() {
  441. startedWG.Done()
  442. defer wg.Done()
  443. require.NoError(t, d.Run(ctx))
  444. }()
  445. // FIXME: used to induce context switch.
  446. // Determine more deterministic method for prompting a context switch
  447. startedWG.Wait()
  448. requireConnect(t, rpcConfig.ListenAddress, 20)
  449. cli, err := httpclient.New(rpcConfig.ListenAddress)
  450. require.NoError(t, err)
  451. testPage := 1
  452. testPerPage := 100
  453. res, err := cli.Validators(ctx, &testHeight, &testPage, &testPerPage)
  454. require.NoError(t, err)
  455. require.NotNil(t, res)
  456. require.Equal(t, testVotingPower, res.Validators[0].VotingPower)
  457. cancel()
  458. wg.Wait()
  459. blockStoreMock.AssertExpectations(t)
  460. stateStoreMock.AssertExpectations(t)
  461. }
  462. func TestBlockSearch(t *testing.T) {
  463. testHeight := int64(1)
  464. testBlockHash := []byte("test hash")
  465. testQuery := "block.height = 1"
  466. stateStoreMock := &statemocks.Store{}
  467. blockStoreMock := &statemocks.BlockStore{}
  468. eventSinkMock := &indexermocks.EventSink{}
  469. eventSinkMock.On("Stop").Return(nil)
  470. eventSinkMock.On("Type").Return(indexer.KV)
  471. blockStoreMock.On("LoadBlock", testHeight).Return(&types.Block{
  472. Header: types.Header{
  473. Height: testHeight,
  474. },
  475. }, nil)
  476. blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{
  477. BlockID: types.BlockID{
  478. Hash: testBlockHash,
  479. },
  480. })
  481. eventSinkMock.On("SearchBlockEvents", mock.Anything,
  482. mock.MatchedBy(func(q *query.Query) bool { return testQuery == q.String() })).
  483. Return([]int64{testHeight}, nil)
  484. rpcConfig := config.TestRPCConfig()
  485. l := log.TestingLogger()
  486. d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l)
  487. ctx, cancel := context.WithCancel(context.Background())
  488. wg := &sync.WaitGroup{}
  489. wg.Add(1)
  490. startedWG := &sync.WaitGroup{}
  491. startedWG.Add(1)
  492. go func() {
  493. startedWG.Done()
  494. defer wg.Done()
  495. require.NoError(t, d.Run(ctx))
  496. }()
  497. // FIXME: used to induce context switch.
  498. // Determine more deterministic method for prompting a context switch
  499. startedWG.Wait()
  500. requireConnect(t, rpcConfig.ListenAddress, 20)
  501. cli, err := httpclient.New(rpcConfig.ListenAddress)
  502. require.NoError(t, err)
  503. testPage := 1
  504. testPerPage := 100
  505. testOrderBy := "desc"
  506. res, err := cli.BlockSearch(ctx, testQuery, &testPage, &testPerPage, testOrderBy)
  507. require.NoError(t, err)
  508. require.NotNil(t, res)
  509. require.Equal(t, testBlockHash, []byte(res.Blocks[0].BlockID.Hash))
  510. cancel()
  511. wg.Wait()
  512. blockStoreMock.AssertExpectations(t)
  513. stateStoreMock.AssertExpectations(t)
  514. }
  515. func requireConnect(t testing.TB, addr string, retries int) {
  516. parts := strings.SplitN(addr, "://", 2)
  517. if len(parts) != 2 {
  518. t.Fatalf("malformed address to dial: %s", addr)
  519. }
  520. var err error
  521. for i := 0; i < retries; i++ {
  522. var conn net.Conn
  523. conn, err = net.Dial(parts[0], parts[1])
  524. if err == nil {
  525. conn.Close()
  526. return
  527. }
  528. // FIXME attempt to yield and let the other goroutine continue execution.
  529. time.Sleep(time.Microsecond * 100)
  530. }
  531. t.Fatalf("unable to connect to server %s after %d tries: %s", addr, retries, err)
  532. }