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