test_util.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. #
  4. """
  5. Tests for L{twisted.trial.util}
  6. """
  7. from __future__ import division, absolute_import
  8. import os, sys
  9. from zope.interface import implementer
  10. from twisted.python.compat import NativeStringIO
  11. from twisted.python import filepath
  12. from twisted.internet.interfaces import IProcessTransport
  13. from twisted.internet import defer
  14. from twisted.internet.base import DelayedCall
  15. from twisted.python.failure import Failure
  16. from twisted.trial.unittest import SynchronousTestCase
  17. from twisted.trial import util
  18. from twisted.trial.util import (
  19. DirtyReactorAggregateError, _Janitor, excInfoOrFailureToExcInfo,
  20. acquireAttribute)
  21. class MktempTests(SynchronousTestCase):
  22. """
  23. Tests for L{TestCase.mktemp}, a helper function for creating temporary file
  24. or directory names.
  25. """
  26. def test_name(self):
  27. """
  28. The path name returned by C{mktemp} is directly beneath a directory
  29. which identifies the test method which created the name.
  30. """
  31. name = self.mktemp()
  32. dirs = os.path.dirname(name).split(os.sep)[:-1]
  33. self.assertEqual(
  34. dirs, ['twisted.trial.test.test_util', 'MktempTests', 'test_name'])
  35. def test_unique(self):
  36. """
  37. Repeated calls to C{mktemp} return different values.
  38. """
  39. name = self.mktemp()
  40. self.assertNotEqual(name, self.mktemp())
  41. def test_created(self):
  42. """
  43. The directory part of the path name returned by C{mktemp} exists.
  44. """
  45. name = self.mktemp()
  46. dirname = os.path.dirname(name)
  47. self.assertTrue(os.path.exists(dirname))
  48. self.assertFalse(os.path.exists(name))
  49. def test_location(self):
  50. """
  51. The path returned by C{mktemp} is beneath the current working directory.
  52. """
  53. path = os.path.abspath(self.mktemp())
  54. self.assertTrue(path.startswith(os.getcwd()))
  55. class RunSequentiallyTests(SynchronousTestCase):
  56. """
  57. Sometimes it is useful to be able to run an arbitrary list of callables,
  58. one after the other.
  59. When some of those callables can return Deferreds, things become complex.
  60. """
  61. def assertDeferredResult(self, deferred, assertFunction, *args, **kwargs):
  62. """
  63. Call the given assertion function against the current result of a
  64. Deferred.
  65. """
  66. result = []
  67. deferred.addCallback(result.append)
  68. assertFunction(result[0], *args, **kwargs)
  69. def test_emptyList(self):
  70. """
  71. When asked to run an empty list of callables, runSequentially returns a
  72. successful Deferred that fires an empty list.
  73. """
  74. d = util._runSequentially([])
  75. self.assertDeferredResult(d, self.assertEqual, [])
  76. def test_singleSynchronousSuccess(self):
  77. """
  78. When given a callable that succeeds without returning a Deferred,
  79. include the return value in the results list, tagged with a SUCCESS
  80. flag.
  81. """
  82. d = util._runSequentially([lambda: None])
  83. self.assertDeferredResult(d, self.assertEqual, [(defer.SUCCESS, None)])
  84. def test_singleSynchronousFailure(self):
  85. """
  86. When given a callable that raises an exception, include a Failure for
  87. that exception in the results list, tagged with a FAILURE flag.
  88. """
  89. d = util._runSequentially([lambda: self.fail('foo')])
  90. def check(results):
  91. [(flag, fail)] = results
  92. fail.trap(self.failureException)
  93. self.assertEqual(fail.getErrorMessage(), 'foo')
  94. self.assertEqual(flag, defer.FAILURE)
  95. self.assertDeferredResult(d, check)
  96. def test_singleAsynchronousSuccess(self):
  97. """
  98. When given a callable that returns a successful Deferred, include the
  99. result of the Deferred in the results list, tagged with a SUCCESS flag.
  100. """
  101. d = util._runSequentially([lambda: defer.succeed(None)])
  102. self.assertDeferredResult(d, self.assertEqual, [(defer.SUCCESS, None)])
  103. def test_singleAsynchronousFailure(self):
  104. """
  105. When given a callable that returns a failing Deferred, include the
  106. failure the results list, tagged with a FAILURE flag.
  107. """
  108. d = util._runSequentially([lambda: defer.fail(ValueError('foo'))])
  109. def check(results):
  110. [(flag, fail)] = results
  111. fail.trap(ValueError)
  112. self.assertEqual(fail.getErrorMessage(), 'foo')
  113. self.assertEqual(flag, defer.FAILURE)
  114. self.assertDeferredResult(d, check)
  115. def test_callablesCalledInOrder(self):
  116. """
  117. Check that the callables are called in the given order, one after the
  118. other.
  119. """
  120. log = []
  121. deferreds = []
  122. def append(value):
  123. d = defer.Deferred()
  124. log.append(value)
  125. deferreds.append(d)
  126. return d
  127. util._runSequentially([lambda: append('foo'),
  128. lambda: append('bar')])
  129. # runSequentially should wait until the Deferred has fired before
  130. # running the second callable.
  131. self.assertEqual(log, ['foo'])
  132. deferreds[-1].callback(None)
  133. self.assertEqual(log, ['foo', 'bar'])
  134. def test_continuesAfterError(self):
  135. """
  136. If one of the callables raises an error, then runSequentially continues
  137. to run the remaining callables.
  138. """
  139. d = util._runSequentially([lambda: self.fail('foo'), lambda: 'bar'])
  140. def check(results):
  141. [(flag1, fail), (flag2, result)] = results
  142. fail.trap(self.failureException)
  143. self.assertEqual(flag1, defer.FAILURE)
  144. self.assertEqual(fail.getErrorMessage(), 'foo')
  145. self.assertEqual(flag2, defer.SUCCESS)
  146. self.assertEqual(result, 'bar')
  147. self.assertDeferredResult(d, check)
  148. def test_stopOnFirstError(self):
  149. """
  150. If the C{stopOnFirstError} option is passed to C{runSequentially}, then
  151. no further callables are called after the first exception is raised.
  152. """
  153. d = util._runSequentially([lambda: self.fail('foo'), lambda: 'bar'],
  154. stopOnFirstError=True)
  155. def check(results):
  156. [(flag1, fail)] = results
  157. fail.trap(self.failureException)
  158. self.assertEqual(flag1, defer.FAILURE)
  159. self.assertEqual(fail.getErrorMessage(), 'foo')
  160. self.assertDeferredResult(d, check)
  161. class DirtyReactorAggregateErrorTests(SynchronousTestCase):
  162. """
  163. Tests for the L{DirtyReactorAggregateError}.
  164. """
  165. def test_formatDelayedCall(self):
  166. """
  167. Delayed calls are formatted nicely.
  168. """
  169. error = DirtyReactorAggregateError(["Foo", "bar"])
  170. self.assertEqual(str(error),
  171. """\
  172. Reactor was unclean.
  173. DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
  174. Foo
  175. bar""")
  176. def test_formatSelectables(self):
  177. """
  178. Selectables are formatted nicely.
  179. """
  180. error = DirtyReactorAggregateError([], ["selectable 1", "selectable 2"])
  181. self.assertEqual(str(error),
  182. """\
  183. Reactor was unclean.
  184. Selectables:
  185. selectable 1
  186. selectable 2""")
  187. def test_formatDelayedCallsAndSelectables(self):
  188. """
  189. Both delayed calls and selectables can appear in the same error.
  190. """
  191. error = DirtyReactorAggregateError(["bleck", "Boozo"],
  192. ["Sel1", "Sel2"])
  193. self.assertEqual(str(error),
  194. """\
  195. Reactor was unclean.
  196. DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
  197. bleck
  198. Boozo
  199. Selectables:
  200. Sel1
  201. Sel2""")
  202. class StubReactor(object):
  203. """
  204. A reactor stub which contains enough functionality to be used with the
  205. L{_Janitor}.
  206. @ivar iterations: A list of the arguments passed to L{iterate}.
  207. @ivar removeAllCalled: Number of times that L{removeAll} was called.
  208. @ivar selectables: The value that will be returned from L{removeAll}.
  209. @ivar delayedCalls: The value to return from L{getDelayedCalls}.
  210. """
  211. def __init__(self, delayedCalls, selectables=None):
  212. """
  213. @param delayedCalls: See L{StubReactor.delayedCalls}.
  214. @param selectables: See L{StubReactor.selectables}.
  215. """
  216. self.delayedCalls = delayedCalls
  217. self.iterations = []
  218. self.removeAllCalled = 0
  219. if not selectables:
  220. selectables = []
  221. self.selectables = selectables
  222. def iterate(self, timeout=None):
  223. """
  224. Increment C{self.iterations}.
  225. """
  226. self.iterations.append(timeout)
  227. def getDelayedCalls(self):
  228. """
  229. Return C{self.delayedCalls}.
  230. """
  231. return self.delayedCalls
  232. def removeAll(self):
  233. """
  234. Increment C{self.removeAllCalled} and return C{self.selectables}.
  235. """
  236. self.removeAllCalled += 1
  237. return self.selectables
  238. class StubErrorReporter(object):
  239. """
  240. A subset of L{twisted.trial.itrial.IReporter} which records L{addError}
  241. calls.
  242. @ivar errors: List of two-tuples of (test, error) which were passed to
  243. L{addError}.
  244. """
  245. def __init__(self):
  246. self.errors = []
  247. def addError(self, test, error):
  248. """
  249. Record parameters in C{self.errors}.
  250. """
  251. self.errors.append((test, error))
  252. class JanitorTests(SynchronousTestCase):
  253. """
  254. Tests for L{_Janitor}!
  255. """
  256. def test_cleanPendingSpinsReactor(self):
  257. """
  258. During pending-call cleanup, the reactor will be spun twice with an
  259. instant timeout. This is not a requirement, it is only a test for
  260. current behavior. Hopefully Trial will eventually not do this kind of
  261. reactor stuff.
  262. """
  263. reactor = StubReactor([])
  264. jan = _Janitor(None, None, reactor=reactor)
  265. jan._cleanPending()
  266. self.assertEqual(reactor.iterations, [0, 0])
  267. def test_cleanPendingCancelsCalls(self):
  268. """
  269. During pending-call cleanup, the janitor cancels pending timed calls.
  270. """
  271. def func():
  272. return "Lulz"
  273. cancelled = []
  274. delayedCall = DelayedCall(300, func, (), {},
  275. cancelled.append, lambda x: None)
  276. reactor = StubReactor([delayedCall])
  277. jan = _Janitor(None, None, reactor=reactor)
  278. jan._cleanPending()
  279. self.assertEqual(cancelled, [delayedCall])
  280. def test_cleanPendingReturnsDelayedCallStrings(self):
  281. """
  282. The Janitor produces string representations of delayed calls from the
  283. delayed call cleanup method. It gets the string representations
  284. *before* cancelling the calls; this is important because cancelling the
  285. call removes critical debugging information from the string
  286. representation.
  287. """
  288. delayedCall = DelayedCall(300, lambda: None, (), {},
  289. lambda x: None, lambda x: None,
  290. seconds=lambda: 0)
  291. delayedCallString = str(delayedCall)
  292. reactor = StubReactor([delayedCall])
  293. jan = _Janitor(None, None, reactor=reactor)
  294. strings = jan._cleanPending()
  295. self.assertEqual(strings, [delayedCallString])
  296. def test_cleanReactorRemovesSelectables(self):
  297. """
  298. The Janitor will remove selectables during reactor cleanup.
  299. """
  300. reactor = StubReactor([])
  301. jan = _Janitor(None, None, reactor=reactor)
  302. jan._cleanReactor()
  303. self.assertEqual(reactor.removeAllCalled, 1)
  304. def test_cleanReactorKillsProcesses(self):
  305. """
  306. The Janitor will kill processes during reactor cleanup.
  307. """
  308. @implementer(IProcessTransport)
  309. class StubProcessTransport(object):
  310. """
  311. A stub L{IProcessTransport} provider which records signals.
  312. @ivar signals: The signals passed to L{signalProcess}.
  313. """
  314. def __init__(self):
  315. self.signals = []
  316. def signalProcess(self, signal):
  317. """
  318. Append C{signal} to C{self.signals}.
  319. """
  320. self.signals.append(signal)
  321. pt = StubProcessTransport()
  322. reactor = StubReactor([], [pt])
  323. jan = _Janitor(None, None, reactor=reactor)
  324. jan._cleanReactor()
  325. self.assertEqual(pt.signals, ["KILL"])
  326. def test_cleanReactorReturnsSelectableStrings(self):
  327. """
  328. The Janitor returns string representations of the selectables that it
  329. cleaned up from the reactor cleanup method.
  330. """
  331. class Selectable(object):
  332. """
  333. A stub Selectable which only has an interesting string
  334. representation.
  335. """
  336. def __repr__(self):
  337. return "(SELECTABLE!)"
  338. reactor = StubReactor([], [Selectable()])
  339. jan = _Janitor(None, None, reactor=reactor)
  340. self.assertEqual(jan._cleanReactor(), ["(SELECTABLE!)"])
  341. def test_postCaseCleanupNoErrors(self):
  342. """
  343. The post-case cleanup method will return True and not call C{addError}
  344. on the result if there are no pending calls.
  345. """
  346. reactor = StubReactor([])
  347. test = object()
  348. reporter = StubErrorReporter()
  349. jan = _Janitor(test, reporter, reactor=reactor)
  350. self.assertTrue(jan.postCaseCleanup())
  351. self.assertEqual(reporter.errors, [])
  352. def test_postCaseCleanupWithErrors(self):
  353. """
  354. The post-case cleanup method will return False and call C{addError} on
  355. the result with a L{DirtyReactorAggregateError} Failure if there are
  356. pending calls.
  357. """
  358. delayedCall = DelayedCall(300, lambda: None, (), {},
  359. lambda x: None, lambda x: None,
  360. seconds=lambda: 0)
  361. delayedCallString = str(delayedCall)
  362. reactor = StubReactor([delayedCall], [])
  363. test = object()
  364. reporter = StubErrorReporter()
  365. jan = _Janitor(test, reporter, reactor=reactor)
  366. self.assertFalse(jan.postCaseCleanup())
  367. self.assertEqual(len(reporter.errors), 1)
  368. self.assertEqual(reporter.errors[0][1].value.delayedCalls,
  369. [delayedCallString])
  370. def test_postClassCleanupNoErrors(self):
  371. """
  372. The post-class cleanup method will not call C{addError} on the result
  373. if there are no pending calls or selectables.
  374. """
  375. reactor = StubReactor([])
  376. test = object()
  377. reporter = StubErrorReporter()
  378. jan = _Janitor(test, reporter, reactor=reactor)
  379. jan.postClassCleanup()
  380. self.assertEqual(reporter.errors, [])
  381. def test_postClassCleanupWithPendingCallErrors(self):
  382. """
  383. The post-class cleanup method call C{addError} on the result with a
  384. L{DirtyReactorAggregateError} Failure if there are pending calls.
  385. """
  386. delayedCall = DelayedCall(300, lambda: None, (), {},
  387. lambda x: None, lambda x: None,
  388. seconds=lambda: 0)
  389. delayedCallString = str(delayedCall)
  390. reactor = StubReactor([delayedCall], [])
  391. test = object()
  392. reporter = StubErrorReporter()
  393. jan = _Janitor(test, reporter, reactor=reactor)
  394. jan.postClassCleanup()
  395. self.assertEqual(len(reporter.errors), 1)
  396. self.assertEqual(reporter.errors[0][1].value.delayedCalls,
  397. [delayedCallString])
  398. def test_postClassCleanupWithSelectableErrors(self):
  399. """
  400. The post-class cleanup method call C{addError} on the result with a
  401. L{DirtyReactorAggregateError} Failure if there are selectables.
  402. """
  403. selectable = "SELECTABLE HERE"
  404. reactor = StubReactor([], [selectable])
  405. test = object()
  406. reporter = StubErrorReporter()
  407. jan = _Janitor(test, reporter, reactor=reactor)
  408. jan.postClassCleanup()
  409. self.assertEqual(len(reporter.errors), 1)
  410. self.assertEqual(reporter.errors[0][1].value.selectables,
  411. [repr(selectable)])
  412. class RemoveSafelyTests(SynchronousTestCase):
  413. """
  414. Tests for L{util._removeSafely}.
  415. """
  416. def test_removeSafelyNoTrialMarker(self):
  417. """
  418. If a path doesn't contain a node named C{"_trial_marker"}, that path is
  419. not removed by L{util._removeSafely} and a L{util._NoTrialMarker}
  420. exception is raised instead.
  421. """
  422. directory = self.mktemp().encode("utf-8")
  423. os.mkdir(directory)
  424. dirPath = filepath.FilePath(directory)
  425. self.assertRaises(util._NoTrialMarker, util._removeSafely, dirPath)
  426. def test_removeSafelyRemoveFailsMoveSucceeds(self):
  427. """
  428. If an L{OSError} is raised while removing a path in
  429. L{util._removeSafely}, an attempt is made to move the path to a new
  430. name.
  431. """
  432. def dummyRemove():
  433. """
  434. Raise an C{OSError} to emulate the branch of L{util._removeSafely}
  435. in which path removal fails.
  436. """
  437. raise OSError()
  438. # Patch stdout so we can check the print statements in _removeSafely
  439. out = NativeStringIO()
  440. self.patch(sys, 'stdout', out)
  441. # Set up a trial directory with a _trial_marker
  442. directory = self.mktemp().encode("utf-8")
  443. os.mkdir(directory)
  444. dirPath = filepath.FilePath(directory)
  445. dirPath.child(b'_trial_marker').touch()
  446. # Ensure that path.remove() raises an OSError
  447. dirPath.remove = dummyRemove
  448. util._removeSafely(dirPath)
  449. self.assertIn("could not remove FilePath", out.getvalue())
  450. def test_removeSafelyRemoveFailsMoveFails(self):
  451. """
  452. If an L{OSError} is raised while removing a path in
  453. L{util._removeSafely}, an attempt is made to move the path to a new
  454. name. If that attempt fails, the L{OSError} is re-raised.
  455. """
  456. def dummyRemove():
  457. """
  458. Raise an C{OSError} to emulate the branch of L{util._removeSafely}
  459. in which path removal fails.
  460. """
  461. raise OSError("path removal failed")
  462. def dummyMoveTo(path):
  463. """
  464. Raise an C{OSError} to emulate the branch of L{util._removeSafely}
  465. in which path movement fails.
  466. """
  467. raise OSError("path movement failed")
  468. # Patch stdout so we can check the print statements in _removeSafely
  469. out = NativeStringIO()
  470. self.patch(sys, 'stdout', out)
  471. # Set up a trial directory with a _trial_marker
  472. directory = self.mktemp().encode("utf-8")
  473. os.mkdir(directory)
  474. dirPath = filepath.FilePath(directory)
  475. dirPath.child(b'_trial_marker').touch()
  476. # Ensure that path.remove() and path.moveTo() both raise OSErrors
  477. dirPath.remove = dummyRemove
  478. dirPath.moveTo = dummyMoveTo
  479. error = self.assertRaises(OSError, util._removeSafely, dirPath)
  480. self.assertEqual(str(error), "path movement failed")
  481. self.assertIn("could not remove FilePath", out.getvalue())
  482. class ExcInfoTests(SynchronousTestCase):
  483. """
  484. Tests for L{excInfoOrFailureToExcInfo}.
  485. """
  486. def test_excInfo(self):
  487. """
  488. L{excInfoOrFailureToExcInfo} returns exactly what it is passed, if it is
  489. passed a tuple like the one returned by L{sys.exc_info}.
  490. """
  491. info = (ValueError, ValueError("foo"), None)
  492. self.assertTrue(info is excInfoOrFailureToExcInfo(info))
  493. def test_failure(self):
  494. """
  495. When called with a L{Failure} instance, L{excInfoOrFailureToExcInfo}
  496. returns a tuple like the one returned by L{sys.exc_info}, with the
  497. elements taken from the type, value, and traceback of the failure.
  498. """
  499. try:
  500. 1 / 0
  501. except:
  502. f = Failure()
  503. self.assertEqual((f.type, f.value, f.tb), excInfoOrFailureToExcInfo(f))
  504. class AcquireAttributeTests(SynchronousTestCase):
  505. """
  506. Tests for L{acquireAttribute}.
  507. """
  508. def test_foundOnEarlierObject(self):
  509. """
  510. The value returned by L{acquireAttribute} is the value of the requested
  511. attribute on the first object in the list passed in which has that
  512. attribute.
  513. """
  514. self.value = value = object()
  515. self.assertTrue(value is acquireAttribute([self, object()], "value"))
  516. def test_foundOnLaterObject(self):
  517. """
  518. The same as L{test_foundOnEarlierObject}, but for the case where the 2nd
  519. element in the object list has the attribute and the first does not.
  520. """
  521. self.value = value = object()
  522. self.assertTrue(value is acquireAttribute([object(), self], "value"))
  523. def test_notFoundException(self):
  524. """
  525. If none of the objects passed in the list to L{acquireAttribute} have
  526. the requested attribute, L{AttributeError} is raised.
  527. """
  528. self.assertRaises(AttributeError, acquireAttribute, [object()], "foo")
  529. def test_notFoundDefault(self):
  530. """
  531. If none of the objects passed in the list to L{acquireAttribute} have
  532. the requested attribute and a default value is given, the default value
  533. is returned.
  534. """
  535. default = object()
  536. self.assertTrue(default is acquireAttribute([object()], "foo", default))
  537. class ListToPhraseTests(SynchronousTestCase):
  538. """
  539. Input is transformed into a string representation of the list,
  540. with each item separated by delimiter (defaulting to a comma) and the final
  541. two being separated by a final delimiter.
  542. """
  543. def test_empty(self):
  544. """
  545. If things is empty, an empty string is returned.
  546. """
  547. sample = []
  548. expected = ''
  549. result = util._listToPhrase(sample, 'and')
  550. self.assertEqual(expected, result)
  551. def test_oneWord(self):
  552. """
  553. With a single item, the item is returned.
  554. """
  555. sample = ['One']
  556. expected = 'One'
  557. result = util._listToPhrase(sample, 'and')
  558. self.assertEqual(expected, result)
  559. def test_twoWords(self):
  560. """
  561. Two words are separated by the final delimiter.
  562. """
  563. sample = ['One', 'Two']
  564. expected = 'One and Two'
  565. result = util._listToPhrase(sample, 'and')
  566. self.assertEqual(expected, result)
  567. def test_threeWords(self):
  568. """
  569. With more than two words, the first two are separated by the delimiter.
  570. """
  571. sample = ['One', 'Two', 'Three']
  572. expected = 'One, Two, and Three'
  573. result = util._listToPhrase(sample, 'and')
  574. self.assertEqual(expected, result)
  575. def test_fourWords(self):
  576. """
  577. If a delimiter is specified, it is used instead of the default comma.
  578. """
  579. sample = ['One', 'Two', 'Three', 'Four']
  580. expected = 'One; Two; Three; or Four'
  581. result = util._listToPhrase(sample, 'or', delimiter='; ')
  582. self.assertEqual(expected, result)
  583. def test_notString(self):
  584. """
  585. If something in things is not a string, it is converted into one.
  586. """
  587. sample = [1, 2, 'three']
  588. expected = '1, 2, and three'
  589. result = util._listToPhrase(sample, 'and')
  590. self.assertEqual(expected, result)
  591. def test_stringTypeError(self):
  592. """
  593. If things is a string, a TypeError is raised.
  594. """
  595. sample = "One, two, three"
  596. error = self.assertRaises(TypeError, util._listToPhrase, sample, 'and')
  597. self.assertEqual(str(error), "Things must be a list or a tuple")
  598. def test_iteratorTypeError(self):
  599. """
  600. If things is an iterator, a TypeError is raised.
  601. """
  602. sample = iter([1, 2, 3])
  603. error = self.assertRaises(TypeError, util._listToPhrase, sample, 'and')
  604. self.assertEqual(str(error), "Things must be a list or a tuple")
  605. def test_generatorTypeError(self):
  606. """
  607. If things is a generator, a TypeError is raised.
  608. """
  609. def sample():
  610. for i in range(2):
  611. yield i
  612. error = self.assertRaises(TypeError, util._listToPhrase, sample, 'and')
  613. self.assertEqual(str(error), "Things must be a list or a tuple")