PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/test/unit/state_test.rb

https://github.com/amatsuda/state_machine
Ruby | 848 lines | 688 code | 159 blank | 1 comment | 1 complexity | dd1018de415c233f11db1f06bcd26588 MD5 | raw file
  1. require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
  2. class StateByDefaultTest < Test::Unit::TestCase
  3. def setup
  4. @machine = StateMachine::Machine.new(Class.new)
  5. @state = StateMachine::State.new(@machine, :parked)
  6. end
  7. def test_should_have_a_machine
  8. assert_equal @machine, @state.machine
  9. end
  10. def test_should_have_a_name
  11. assert_equal :parked, @state.name
  12. end
  13. def test_should_have_a_qualified_name
  14. assert_equal :parked, @state.name
  15. end
  16. def test_should_have_a_human_name
  17. assert_equal 'parked', @state.human_name
  18. end
  19. def test_should_use_stringify_the_name_as_the_value
  20. assert_equal 'parked', @state.value
  21. end
  22. def test_should_not_be_initial
  23. assert !@state.initial
  24. end
  25. def test_should_not_have_a_matcher
  26. assert_nil @state.matcher
  27. end
  28. def test_should_not_have_any_methods
  29. expected = {}
  30. assert_equal expected, @state.methods
  31. end
  32. end
  33. class StateTest < Test::Unit::TestCase
  34. def setup
  35. @machine = StateMachine::Machine.new(Class.new)
  36. @state = StateMachine::State.new(@machine, :parked)
  37. end
  38. def test_should_raise_exception_if_invalid_option_specified
  39. exception = assert_raise(ArgumentError) {StateMachine::State.new(@machine, :parked, :invalid => true)}
  40. assert_equal 'Invalid key(s): invalid', exception.message
  41. end
  42. def test_should_allow_changing_machine
  43. new_machine = StateMachine::Machine.new(Class.new)
  44. @state.machine = new_machine
  45. assert_equal new_machine, @state.machine
  46. end
  47. def test_should_allow_changing_value
  48. @state.value = 1
  49. assert_equal 1, @state.value
  50. end
  51. def test_should_allow_changing_initial
  52. @state.initial = true
  53. assert @state.initial
  54. end
  55. def test_should_allow_changing_matcher
  56. matcher = lambda {}
  57. @state.matcher = matcher
  58. assert_equal matcher, @state.matcher
  59. end
  60. def test_should_allow_changing_human_name
  61. @state.human_name = 'stopped'
  62. assert_equal 'stopped', @state.human_name
  63. end
  64. def test_should_use_pretty_inspect
  65. assert_equal '#<StateMachine::State name=:parked value="parked" initial=false context=[]>', @state.inspect
  66. end
  67. end
  68. class StateWithoutNameTest < Test::Unit::TestCase
  69. def setup
  70. @klass = Class.new
  71. @machine = StateMachine::Machine.new(@klass)
  72. @state = StateMachine::State.new(@machine, nil)
  73. end
  74. def test_should_have_a_nil_name
  75. assert_nil @state.name
  76. end
  77. def test_should_have_a_nil_qualified_name
  78. assert_nil @state.qualified_name
  79. end
  80. def test_should_have_an_empty_human_name
  81. assert_equal 'nil', @state.human_name
  82. end
  83. def test_should_have_a_nil_value
  84. assert_nil @state.value
  85. end
  86. def test_should_not_redefine_nil_predicate
  87. object = @klass.new
  88. assert !object.nil?
  89. assert !object.respond_to?('?')
  90. end
  91. def test_should_have_a_description
  92. assert_equal 'nil', @state.description
  93. end
  94. end
  95. class StateWithNameTest < Test::Unit::TestCase
  96. def setup
  97. @klass = Class.new
  98. @machine = StateMachine::Machine.new(@klass)
  99. @state = StateMachine::State.new(@machine, :parked)
  100. end
  101. def test_should_have_a_name
  102. assert_equal :parked, @state.name
  103. end
  104. def test_should_have_a_qualified_name
  105. assert_equal :parked, @state.name
  106. end
  107. def test_should_have_a_human_name
  108. assert_equal 'parked', @state.human_name
  109. end
  110. def test_should_use_stringify_the_name_as_the_value
  111. assert_equal 'parked', @state.value
  112. end
  113. def test_should_match_stringified_name
  114. assert @state.matches?('parked')
  115. assert !@state.matches?('idling')
  116. end
  117. def test_should_not_include_value_in_description
  118. assert_equal 'parked', @state.description
  119. end
  120. def test_should_define_predicate
  121. assert @klass.new.respond_to?(:parked?)
  122. end
  123. end
  124. class StateWithNilValueTest < Test::Unit::TestCase
  125. def setup
  126. @klass = Class.new
  127. @machine = StateMachine::Machine.new(@klass)
  128. @state = StateMachine::State.new(@machine, :parked, :value => nil)
  129. end
  130. def test_should_have_a_name
  131. assert_equal :parked, @state.name
  132. end
  133. def test_should_have_a_nil_value
  134. assert_nil @state.value
  135. end
  136. def test_should_match_nil_values
  137. assert @state.matches?(nil)
  138. end
  139. def test_should_have_a_description
  140. assert_equal 'parked (nil)', @state.description
  141. end
  142. def test_should_define_predicate
  143. object = @klass.new
  144. assert object.respond_to?(:parked?)
  145. end
  146. end
  147. class StateWithSymbolicValueTest < Test::Unit::TestCase
  148. def setup
  149. @klass = Class.new
  150. @machine = StateMachine::Machine.new(@klass)
  151. @state = StateMachine::State.new(@machine, :parked, :value => :parked)
  152. end
  153. def test_should_use_custom_value
  154. assert_equal :parked, @state.value
  155. end
  156. def test_should_not_include_value_in_description
  157. assert_equal 'parked', @state.description
  158. end
  159. def test_should_match_symbolic_value
  160. assert @state.matches?(:parked)
  161. assert !@state.matches?('parked')
  162. end
  163. def test_should_define_predicate
  164. object = @klass.new
  165. assert object.respond_to?(:parked?)
  166. end
  167. end
  168. class StateWithIntegerValueTest < Test::Unit::TestCase
  169. def setup
  170. @klass = Class.new
  171. @machine = StateMachine::Machine.new(@klass)
  172. @state = StateMachine::State.new(@machine, :parked, :value => 1)
  173. end
  174. def test_should_use_custom_value
  175. assert_equal 1, @state.value
  176. end
  177. def test_should_include_value_in_description
  178. assert_equal 'parked (1)', @state.description
  179. end
  180. def test_should_match_integer_value
  181. assert @state.matches?(1)
  182. assert !@state.matches?(2)
  183. end
  184. def test_should_define_predicate
  185. object = @klass.new
  186. assert object.respond_to?(:parked?)
  187. end
  188. end
  189. class StateWithLambdaValueTest < Test::Unit::TestCase
  190. def setup
  191. @klass = Class.new
  192. @args = nil
  193. @machine = StateMachine::Machine.new(@klass)
  194. @value = lambda {|*args| @args = args; :parked}
  195. @state = StateMachine::State.new(@machine, :parked, :value => @value)
  196. end
  197. def test_should_use_evaluated_value_by_default
  198. assert_equal :parked, @state.value
  199. end
  200. def test_should_allow_access_to_original_value
  201. assert_equal @value, @state.value(false)
  202. end
  203. def test_should_include_masked_value_in_description
  204. assert_equal 'parked (*)', @state.description
  205. end
  206. def test_should_not_pass_in_any_arguments
  207. @state.value
  208. assert_equal [], @args
  209. end
  210. def test_should_define_predicate
  211. object = @klass.new
  212. assert object.respond_to?(:parked?)
  213. end
  214. def test_should_match_evaluated_value
  215. assert @state.matches?(:parked)
  216. end
  217. end
  218. class StateWithCachedLambdaValueTest < Test::Unit::TestCase
  219. def setup
  220. @klass = Class.new
  221. @machine = StateMachine::Machine.new(@klass)
  222. @dynamic_value = lambda {'value'}
  223. @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => @dynamic_value, :cache => true)
  224. end
  225. def test_should_be_caching
  226. assert @state.cache
  227. end
  228. def test_should_evaluate_value
  229. assert_equal 'value', @state.value
  230. end
  231. def test_should_only_evaluate_value_once
  232. value = @state.value
  233. assert_same value, @state.value
  234. end
  235. def test_should_update_value_index_for_state_collection
  236. @state.value
  237. assert_equal @state, @machine.states['value', :value]
  238. assert_nil @machine.states[@dynamic_value, :value]
  239. end
  240. end
  241. class StateWithoutCachedLambdaValueTest < Test::Unit::TestCase
  242. def setup
  243. @klass = Class.new
  244. @machine = StateMachine::Machine.new(@klass)
  245. @dynamic_value = lambda {'value'}
  246. @machine.states << @state = StateMachine::State.new(@machine, :parked, :value => @dynamic_value)
  247. end
  248. def test_should_not_be_caching
  249. assert !@state.cache
  250. end
  251. def test_should_evaluate_value_each_time
  252. value = @state.value
  253. assert_not_same value, @state.value
  254. end
  255. def test_should_not_update_value_index_for_state_collection
  256. @state.value
  257. assert_nil @machine.states['value', :value]
  258. assert_equal @state, @machine.states[@dynamic_value, :value]
  259. end
  260. end
  261. class StateWithMatcherTest < Test::Unit::TestCase
  262. def setup
  263. @klass = Class.new
  264. @args = nil
  265. @machine = StateMachine::Machine.new(@klass)
  266. @state = StateMachine::State.new(@machine, :parked, :if => lambda {|value| value == 1})
  267. end
  268. def test_should_not_match_actual_value
  269. assert !@state.matches?('parked')
  270. end
  271. def test_should_match_evaluated_block
  272. assert @state.matches?(1)
  273. end
  274. end
  275. class StateWithHumanNameTest < Test::Unit::TestCase
  276. def setup
  277. @klass = Class.new
  278. @machine = StateMachine::Machine.new(@klass)
  279. @state = StateMachine::State.new(@machine, :parked, :human_name => 'stopped')
  280. end
  281. def test_should_use_custom_human_name
  282. assert_equal 'stopped', @state.human_name
  283. end
  284. end
  285. class StateWithDynamicHumanNameTest < Test::Unit::TestCase
  286. def setup
  287. @klass = Class.new
  288. @machine = StateMachine::Machine.new(@klass)
  289. @state = StateMachine::State.new(@machine, :parked, :human_name => lambda {|state, object| ['stopped', object]})
  290. end
  291. def test_should_use_custom_human_name
  292. human_name, klass = @state.human_name
  293. assert_equal 'stopped', human_name
  294. assert_equal @klass, klass
  295. end
  296. def test_should_allow_custom_class_to_be_passed_through
  297. human_name, klass = @state.human_name(1)
  298. assert_equal 'stopped', human_name
  299. assert_equal 1, klass
  300. end
  301. def test_should_not_cache_value
  302. assert_not_same @state.human_name, @state.human_name
  303. end
  304. end
  305. class StateInitialTest < Test::Unit::TestCase
  306. def setup
  307. @machine = StateMachine::Machine.new(Class.new)
  308. @state = StateMachine::State.new(@machine, :parked, :initial => true)
  309. end
  310. def test_should_be_initial
  311. assert @state.initial
  312. assert @state.initial?
  313. end
  314. end
  315. class StateNotInitialTest < Test::Unit::TestCase
  316. def setup
  317. @machine = StateMachine::Machine.new(Class.new)
  318. @state = StateMachine::State.new(@machine, :parked, :initial => false)
  319. end
  320. def test_should_not_be_initial
  321. assert !@state.initial
  322. assert !@state.initial?
  323. end
  324. end
  325. class StateFinalTest < Test::Unit::TestCase
  326. def setup
  327. @machine = StateMachine::Machine.new(Class.new)
  328. @state = StateMachine::State.new(@machine, :parked)
  329. end
  330. def test_should_be_final_without_input_transitions
  331. assert @state.final?
  332. end
  333. def test_should_be_final_with_input_transitions
  334. @machine.event :park do
  335. transition :idling => :parked
  336. end
  337. assert @state.final?
  338. end
  339. def test_should_be_final_with_loopback
  340. @machine.event :ignite do
  341. transition :parked => same
  342. end
  343. assert @state.final?
  344. end
  345. end
  346. class StateNotFinalTest < Test::Unit::TestCase
  347. def setup
  348. @machine = StateMachine::Machine.new(Class.new)
  349. @state = StateMachine::State.new(@machine, :parked)
  350. end
  351. def test_should_not_be_final_with_outgoing_whitelist_transitions
  352. @machine.event :ignite do
  353. transition :parked => :idling
  354. end
  355. assert !@state.final?
  356. end
  357. def test_should_not_be_final_with_outgoing_all_transitions
  358. @machine.event :ignite do
  359. transition all => :idling
  360. end
  361. assert !@state.final?
  362. end
  363. def test_should_not_be_final_with_outgoing_blacklist_transitions
  364. @machine.event :ignite do
  365. transition all - :first_gear => :idling
  366. end
  367. assert !@state.final?
  368. end
  369. end
  370. class StateWithConflictingHelpersTest < Test::Unit::TestCase
  371. def setup
  372. @klass = Class.new do
  373. def parked?
  374. 0
  375. end
  376. end
  377. @machine = StateMachine::Machine.new(@klass)
  378. @machine.state :parked
  379. @object = @klass.new
  380. end
  381. def test_should_not_redefine_state_predicate
  382. assert_equal 0, @object.parked?
  383. end
  384. def test_should_allow_super_chaining
  385. @klass.class_eval do
  386. def parked?
  387. super ? 1 : 0
  388. end
  389. end
  390. assert_equal 0, @object.parked?
  391. end
  392. end
  393. class StateWithNamespaceTest < Test::Unit::TestCase
  394. def setup
  395. @klass = Class.new
  396. @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
  397. @state = StateMachine::State.new(@machine, :active)
  398. @object = @klass.new
  399. end
  400. def test_should_have_a_name
  401. assert_equal :active, @state.name
  402. end
  403. def test_should_have_a_qualified_name
  404. assert_equal :alarm_active, @state.qualified_name
  405. end
  406. def test_should_namespace_predicate
  407. assert @object.respond_to?(:alarm_active?)
  408. end
  409. end
  410. class StateAfterBeingCopiedTest < Test::Unit::TestCase
  411. def setup
  412. @machine = StateMachine::Machine.new(Class.new)
  413. @state = StateMachine::State.new(@machine, :parked)
  414. @copied_state = @state.dup
  415. end
  416. def test_should_not_have_the_same_collection_of_methods
  417. assert_not_same @state.methods, @copied_state.methods
  418. end
  419. end
  420. class StateWithContextTest < Test::Unit::TestCase
  421. def setup
  422. @klass = Class.new
  423. @machine = StateMachine::Machine.new(@klass)
  424. @ancestors = @klass.ancestors
  425. @state = StateMachine::State.new(@machine, :idling)
  426. speed_method = nil
  427. rpm_method = nil
  428. @state.context do
  429. def speed
  430. 0
  431. end
  432. speed_method = instance_method(:speed)
  433. def rpm
  434. 1000
  435. end
  436. rpm_method = instance_method(:rpm)
  437. end
  438. @speed_method = speed_method
  439. @rpm_method = rpm_method
  440. end
  441. def test_should_include_new_module_in_owner_class
  442. assert_not_equal @ancestors, @klass.ancestors
  443. assert_equal 1, @klass.ancestors.size - @ancestors.size
  444. end
  445. def test_should_define_each_context_method_in_owner_class
  446. %w(speed rpm).each {|method| assert @klass.method_defined?(method)}
  447. end
  448. def test_should_not_use_context_methods_as_owner_class_methods
  449. assert_not_equal @speed_method, @klass.instance_method(:speed)
  450. assert_not_equal @rpm_method, @klass.instance_method(:rpm)
  451. end
  452. def test_should_include_context_methods_in_state_methods
  453. assert_equal @speed_method, @state.methods[:speed]
  454. assert_equal @rpm_method, @state.methods[:rpm]
  455. end
  456. end
  457. class StateWithMultipleContextsTest < Test::Unit::TestCase
  458. def setup
  459. @klass = Class.new
  460. @machine = StateMachine::Machine.new(@klass)
  461. @ancestors = @klass.ancestors
  462. @state = StateMachine::State.new(@machine, :idling)
  463. speed_method = nil
  464. @state.context do
  465. def speed
  466. 0
  467. end
  468. speed_method = instance_method(:speed)
  469. end
  470. @speed_method = speed_method
  471. rpm_method = nil
  472. @state.context do
  473. def rpm
  474. 1000
  475. end
  476. rpm_method = instance_method(:rpm)
  477. end
  478. @rpm_method = rpm_method
  479. end
  480. def test_should_include_new_module_in_owner_class
  481. assert_not_equal @ancestors, @klass.ancestors
  482. assert_equal 2, @klass.ancestors.size - @ancestors.size
  483. end
  484. def test_should_define_each_context_method_in_owner_class
  485. %w(speed rpm).each {|method| assert @klass.method_defined?(method)}
  486. end
  487. def test_should_not_use_context_methods_as_owner_class_methods
  488. assert_not_equal @speed_method, @klass.instance_method(:speed)
  489. assert_not_equal @rpm_method, @klass.instance_method(:rpm)
  490. end
  491. def test_should_include_context_methods_in_state_methods
  492. assert_equal @speed_method, @state.methods[:speed]
  493. assert_equal @rpm_method, @state.methods[:rpm]
  494. end
  495. end
  496. class StateWithExistingContextMethodTest < Test::Unit::TestCase
  497. def setup
  498. @klass = Class.new do
  499. def speed
  500. 60
  501. end
  502. end
  503. @original_speed_method = @klass.instance_method(:speed)
  504. @machine = StateMachine::Machine.new(@klass)
  505. @state = StateMachine::State.new(@machine, :idling)
  506. @state.context do
  507. def speed
  508. 0
  509. end
  510. end
  511. end
  512. def test_should_not_override_method
  513. assert_equal @original_speed_method, @klass.instance_method(:speed)
  514. end
  515. end
  516. class StateWithRedefinedContextMethodTest < Test::Unit::TestCase
  517. def setup
  518. @klass = Class.new
  519. @machine = StateMachine::Machine.new(@klass)
  520. @state = StateMachine::State.new(@machine, 'on')
  521. old_speed_method = nil
  522. @state.context do
  523. def speed
  524. 0
  525. end
  526. old_speed_method = instance_method(:speed)
  527. end
  528. @old_speed_method = old_speed_method
  529. current_speed_method = nil
  530. @state.context do
  531. def speed
  532. 'green'
  533. end
  534. current_speed_method = instance_method(:speed)
  535. end
  536. @current_speed_method = current_speed_method
  537. end
  538. def test_should_track_latest_defined_method
  539. assert_equal @current_speed_method, @state.methods[:speed]
  540. end
  541. end
  542. class StateWithInvalidMethodCallTest < Test::Unit::TestCase
  543. def setup
  544. @klass = Class.new
  545. @machine = StateMachine::Machine.new(@klass)
  546. @ancestors = @klass.ancestors
  547. @state = StateMachine::State.new(@machine, :idling)
  548. @state.context do
  549. def speed
  550. 0
  551. end
  552. end
  553. @object = @klass.new
  554. end
  555. def test_should_call_method_missing_arg
  556. assert_equal 1, @state.call(@object, :invalid, lambda {1})
  557. end
  558. end
  559. class StateWithValidMethodCallTest < Test::Unit::TestCase
  560. def setup
  561. @klass = Class.new
  562. @machine = StateMachine::Machine.new(@klass)
  563. @ancestors = @klass.ancestors
  564. @state = StateMachine::State.new(@machine, :idling)
  565. @state.context do
  566. def speed(arg = nil)
  567. block_given? ? [arg, yield] : arg
  568. end
  569. end
  570. @object = @klass.new
  571. end
  572. def test_should_not_raise_an_exception
  573. assert_nothing_raised { @state.call(@object, :speed, lambda {raise}) }
  574. end
  575. def test_should_pass_arguments_through
  576. assert_equal 1, @state.call(@object, :speed, lambda {}, 1)
  577. end
  578. def test_should_pass_blocks_through
  579. assert_equal [nil, 1], @state.call(@object, :speed) {1}
  580. end
  581. def test_should_pass_both_arguments_and_blocks_through
  582. assert_equal [1, 2], @state.call(@object, :speed, lambda {}, 1) {2}
  583. end
  584. end
  585. begin
  586. # Load library
  587. require 'rubygems'
  588. gem 'ruby-graphviz', '>=0.9.0'
  589. require 'graphviz'
  590. class StateDrawingTest < Test::Unit::TestCase
  591. def setup
  592. @machine = StateMachine::Machine.new(Class.new)
  593. @machine.event :ignite do
  594. transition :parked => :idling
  595. end
  596. @state = StateMachine::State.new(@machine, :parked, :value => 1)
  597. graph = GraphViz.new('G')
  598. @node = @state.draw(graph)
  599. end
  600. def test_should_use_ellipse_shape
  601. assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '')
  602. end
  603. def test_should_set_width_to_one
  604. assert_equal '1', @node['width'].to_s.gsub('"', '')
  605. end
  606. def test_should_set_height_to_one
  607. assert_equal '1', @node['height'].to_s.gsub('"', '')
  608. end
  609. def test_should_use_stringified_name_as_name
  610. assert_equal 'parked', @node.name
  611. end
  612. def test_should_use_description_as_label
  613. assert_equal 'parked (1)', @node['label'].to_s.gsub('"', '')
  614. end
  615. end
  616. class StateDrawingInitialTest < Test::Unit::TestCase
  617. def setup
  618. @machine = StateMachine::Machine.new(Class.new)
  619. @machine.event :ignite do
  620. transition :parked => :idling
  621. end
  622. @state = StateMachine::State.new(@machine, :parked, :initial => true)
  623. @graph = GraphViz.new('G')
  624. @node = @state.draw(@graph)
  625. end
  626. def test_should_use_ellipse_as_shape
  627. assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '')
  628. end
  629. def test_should_draw_edge_between_point_and_state
  630. assert_equal 2, @graph.node_count
  631. assert_equal 1, @graph.edge_count
  632. end
  633. end
  634. class StateDrawingNilNameTest < Test::Unit::TestCase
  635. def setup
  636. @machine = StateMachine::Machine.new(Class.new)
  637. @state = StateMachine::State.new(@machine, nil)
  638. graph = GraphViz.new('G')
  639. @node = @state.draw(graph)
  640. end
  641. def test_should_use_stringified_nil_as_name
  642. assert_equal 'nil', @node.name
  643. end
  644. def test_should_use_description_as_label
  645. assert_equal 'nil', @node['label'].to_s.gsub('"', '')
  646. end
  647. end
  648. class StateDrawingLambdaValueTest < Test::Unit::TestCase
  649. def setup
  650. @machine = StateMachine::Machine.new(Class.new)
  651. @state = StateMachine::State.new(@machine, :parked, :value => lambda {})
  652. graph = GraphViz.new('G')
  653. @node = @state.draw(graph)
  654. end
  655. def test_should_use_stringified_name_as_name
  656. assert_equal 'parked', @node.name
  657. end
  658. def test_should_use_description_as_label
  659. assert_equal 'parked (*)', @node['label'].to_s.gsub('"', '')
  660. end
  661. end
  662. class StateDrawingNonFinalTest < Test::Unit::TestCase
  663. def setup
  664. @machine = StateMachine::Machine.new(Class.new)
  665. @machine.event :ignite do
  666. transition :parked => :idling
  667. end
  668. @state = StateMachine::State.new(@machine, :parked)
  669. graph = GraphViz.new('G')
  670. @node = @state.draw(graph)
  671. end
  672. def test_should_use_ellipse_as_shape
  673. assert_equal 'ellipse', @node['shape'].to_s.gsub('"', '')
  674. end
  675. end
  676. class StateDrawingFinalTest < Test::Unit::TestCase
  677. def setup
  678. @machine = StateMachine::Machine.new(Class.new)
  679. @state = StateMachine::State.new(@machine, :parked)
  680. graph = GraphViz.new('G')
  681. @node = @state.draw(graph)
  682. end
  683. def test_should_use_doublecircle_as_shape
  684. assert_equal 'doublecircle', @node['shape'].to_s.gsub('"', '')
  685. end
  686. end
  687. rescue LoadError
  688. $stderr.puts 'Skipping GraphViz StateMachine::State tests. `gem install ruby-graphviz` >= v0.9.0 and try again.'
  689. end