PageRenderTime 48ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/test/statemodelcreator__tests.rb

http://errfix.googlecode.com/
Ruby | 601 lines | 340 code | 138 blank | 123 comment | 12 complexity | 4f1c744efad8714cfc12810a239b2a5b MD5 | raw file
  1. # Copyright (c) 2008 Peter Houghton
  2. #
  3. # Permission is hereby granted, free of charge, to any person
  4. # obtaining a copy of this software and associated documentation
  5. # files (the "Software"), to deal in the Software without
  6. # restriction, including without limitation the rights to use,
  7. # copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. # copies of the Software, and to permit persons to whom the
  9. # Software is furnished to do so, subject to the following
  10. # conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  17. # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  19. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  22. # OTHER DEALINGS IN THE SOFTWARE.
  23. require 'rubygems'
  24. require 'test/unit'
  25. require 'errfix'
  26. require 'eg_test_driver'
  27. class StateModelCreator__tests < Test::Unit::TestCase
  28. # 1 Dimensional State tables
  29. TEST1_CSV="test1.csv" # Simple 2 state 2 action table
  30. TEST2_1LINE_CSV="test2.csv" # Only has 1 line
  31. TEST3_0LINE_CSV="test3.csv" # Has no lines
  32. TEST4_CSV="test4.csv" # Line with fork, UNIX format txt
  33. TEST5_CSV="test5.csv" # Several states in a row, single file
  34. TEST9_CSV="test9.csv" # Line with loop backs, DOS format txt
  35. TEST10_CSV="test10.csv" # Line with fork, DOS format txt
  36. # 2 Dimensional State tables
  37. TEST1_2d_CSV="test1_2d.csv" # Simple 2 state 2 action table
  38. TEST2_2d_1LINE_CSV="test2_2d.csv" # Only has 1 line
  39. TEST3_2d_0LINE_CSV="test3_2d.csv" # Has no lines
  40. TEST4_2d_CSV="test4_2d.csv" # Line with fork, UNIX format txt
  41. TEST5_2d_CSV="test5_2d.csv" # Several states in a row, single file
  42. TEST9_2d_CSV="test9_2d.csv" # Line with loop backs, DOS Format txt
  43. TEST10_2d_CSV="test10_2d.csv" # Line with fork, DOS format txt
  44. # DSL version of the above:
  45. TEST9_DSL="test9_dsl.rb" # Line with loop backs, DOS Format txt
  46. TEST10_DSL="test10_dsl.rb" # Line with fork, DOS format txt
  47. def setup
  48. @path = File.expand_path(File.dirname(__FILE__) + "/../test")
  49. @path = @path + "/"
  50. # For 1-Dimensional State tables
  51. @valid_csv_files_1d=[@path + TEST1_CSV,@path + TEST4_CSV,@path + TEST10_CSV,@path + TEST9_CSV]
  52. puts @valid_csv_files_1d
  53. @valid_csv_files_states_1d=[2,5,5,5]
  54. @valid_csv_files_transitions_1d=[2,4,4,8]
  55. # For 2-Dimensional State tables
  56. @valid_csv_files_2d=[@path + TEST1_2d_CSV,@path + TEST4_2d_CSV,@path + TEST10_2d_CSV,@path + TEST9_2d_CSV]
  57. puts @valid_csv_files_2d
  58. @valid_csv_files_states_2d=[2,5,5,5]
  59. @valid_csv_files_transitions_2d=[2,4,4,8]
  60. # For 1 and 2 Dimensional State tables
  61. @valid_csv_files=@valid_csv_files_1d + @valid_csv_files_2d
  62. puts @valid_csv_files
  63. @valid_csv_files_states=[2,5,5,5,2,5,5,5]
  64. @valid_csv_files_transitions=[2,4,4,8,2,4,4,8]
  65. end # end setup method
  66. # Check that errfix can correctly detect whether the CSV represents
  67. # a 1 or 2 dimenional State table.
  68. #
  69. # 1 Dimensional
  70. def test_detect_state_table_one_dimensional
  71. @valid_csv_files_1d.each do |csv_file|
  72. smc = StateModelCreator.new
  73. dimensions = smc.detect_state_table_dim(csv_file)
  74. assert_equal(:one_d , dimensions ,"Check that one dimensional state tables are detected in #{csv_file}")
  75. end # end csv files
  76. end # end test method
  77. # 2 Dimensional
  78. def test_detect_state_table_two_dimensional
  79. @valid_csv_files_2d.each do |csv_file|
  80. smc = StateModelCreator.new
  81. dimensions = smc.detect_state_table_dim(csv_file)
  82. assert_equal(:two_d , dimensions ,"Check that Two dimensional state tables are detected in #{csv_file}")
  83. end # end csv files
  84. end # end test method
  85. # Neither 1 or 2 D, should raise RuntimrError
  86. def test_detect_state_table__empty_file
  87. assert_raises RuntimeError do
  88. smc = StateModelCreator.new
  89. puts "Next test_detect_state_table__empty_file: "
  90. smc.detect_state_table_dim(@path + TEST3_0LINE_CSV)
  91. end # Assert Raises
  92. end # end test method
  93. def test_detect_state_table_missing_file
  94. # Missing CSV file
  95. assert_raises RuntimeError do
  96. smc = StateModelCreator.new
  97. smc.detect_state_table_dim("madeupname_that_just_aint_real.csv")
  98. end # Assert Raises
  99. end # end method
  100. # Check that load_table returns a copy of the state machine
  101. # This makes it easier and cleaner to use.
  102. def test_load_table_return
  103. smc = StateModelCreator.new
  104. returned_obj = smc.load_table(@path + TEST1_CSV)
  105. assert( returned_obj.instance_of?(StateMachine) , "Check loadtable returns StateMachine, returns: #{returned_obj.class}" )
  106. end # end load_table test
  107. def test_state_store_general
  108. smc = StateModelCreator.new
  109. sm = smc.load_table(@path + TEST1_CSV)
  110. # Check that the states store is an array of strings
  111. assert_equal(Array.new.class,sm.states_store.class, "States Store is an Array")
  112. assert_equal(String.new.class ,sm.states_store[0].class, "States Store Array contains Strings")
  113. # Check that the correct number of states were added
  114. assert(sm.states_store.length==2)
  115. # Check that the states store contains the 2 states in the csv
  116. assert((sm.states_store[0]=="STATEA")||(sm.states_store[0]=="STATEB") , "States store pos 0 is A or B")
  117. assert((sm.states_store[1]=="STATEA")||(sm.states_store[1]=="STATEB") , "States store pos 1 is A or B")
  118. assert((sm.states_store[0] != sm.states_store[1] ) , "States store, the states are not the same...")
  119. end # end test
  120. # Test that the to_s producs a string
  121. # Simple check that the correct Type of object is produced.
  122. # Tests should probably do a simple parse, for known words.
  123. def test_to_s
  124. @valid_csv_files.each do |csv_file|
  125. smc = StateModelCreator.new
  126. sm = smc.load_table(csv_file)
  127. assert_equal(String.new.class , sm.to_s.class , "Check that the to_s produces a string ok")
  128. puts sm.to_s
  129. end # end csv files
  130. end # end test to_s
  131. # Get Actions For State
  132. # Check that the state returns correct actions
  133. def test_get_actions_for_state
  134. smc = StateModelCreator.new
  135. sm = smc.load_table(@path + TEST1_CSV)
  136. # Check that correct actions are returned
  137. assert_equal("action1",sm.get_actions_for_state("STATEA")[0],"Check first action is action1")
  138. assert_equal("action2",sm.get_actions_for_state("STATEB")[0],"Check second action is action2")
  139. end # test end
  140. def test_check_state_table_store
  141. @valid_csv_files.each_index do |index|
  142. csv_file=@valid_csv_files[index]
  143. smc = StateModelCreator.new
  144. sm = smc.load_table(csv_file)
  145. # Check that Adjacency Matrix is ok, this is actually the adjacency matrix
  146. assert_equal(Hash.new.class,sm.adjacency_matrix.class, "Check state is the right class in #{csv_file}.")
  147. assert_equal(@valid_csv_files_states[index],sm.adjacency_matrix.keys.length, "Check correct number of states in #{csv_file}")
  148. transition_obj=sm.adjacency_matrix.shift[1][0]
  149. assert_equal(TransitionHolder.new.class , transition_obj.class, "Check hash is full of transitions in #{csv_file}")
  150. assert_equal(String.new.class , transition_obj.start_state.class , "Check start state is a string class in #{csv_file}")
  151. end # end each file
  152. end # end test
  153. # Create Dot Graph
  154. #
  155. # Creates dot graph (GraphViz) and then checks that it can be written out.
  156. # This helps to ensure its a kosher graphviz object.
  157. #
  158. def test_create_dot_graph_csv
  159. @valid_csv_files.each do |csv_file|
  160. puts csv_file
  161. smc = StateModelCreator.new
  162. sm = smc.load_table(csv_file)
  163. # Create the Graphiz graph object, see if it fails...
  164. sm_graph = sm.create_dot_graph
  165. # Check that the graph produced is at least of the correct class
  166. assert_equal(Graph.new.class, sm_graph.class ,"Check graph is instance of graph class")
  167. # Output DOT version of the graph, see if it fails...
  168. file_name=csv_file.sub(/\.csv$/ , '_csv')
  169. sm_graph.output("#{file_name}.dot")
  170. assert(File.exist?("#{file_name}.dot"),"Check the graph file: #{file_name}.dot was written out.")
  171. end # end valid csvs
  172. end # end test method
  173. # Create Dot Graph
  174. #
  175. # Creates dot graph (GraphViz) and then checks that it can be written out.
  176. # This helps to ensure its a kosher graphviz object.
  177. #
  178. def build_dsl_10
  179. smc =StateModelCreator.new
  180. smc.define_action :action1 do
  181. @action1_done=true
  182. end # end action
  183. smc.define_action :action2
  184. smc.define_guard_on :action2 do
  185. if @action1_done
  186. guard=true
  187. else
  188. guard=false
  189. end # end if
  190. guard
  191. end # end guard
  192. smc.define_action :action3
  193. smc.define_action :action4
  194. smc.attach_transition(:STATEA,:action1,:STATEB)
  195. smc.attach_transition(:STATEB,:action2,:STATEC)
  196. smc.attach_transition(:STATEC,:action3,:STATED)
  197. smc.attach_transition(:STATEC,:action4,:STATEE)
  198. sm = smc.state_machine
  199. return sm
  200. end # end def
  201. def test_create_dot_graph_dsl
  202. sm = build_dsl_10
  203. sm_graph = sm.create_dot_graph
  204. assert(sm_graph.to_s.scan(/Guard\//).length==1 , "Check That a Guard has been added")
  205. sm_graph.output("test10_dsl.dot")
  206. assert(File.exist?("test10_dsl.dot"),"Check the graph file: test10_dsl.dot was written out.")
  207. end # end method
  208. # Random Walk
  209. #
  210. def test_random_walk_simple
  211. smc = StateModelCreator.new
  212. sm = smc.load_table(@path + TEST1_CSV)
  213. # Check standard length walk
  214. the_walk = sm.random_walk("STATEA")
  215. assert_equal(Walk.new.class , the_walk.class , "Check random walk returns Walk instance" )
  216. assert_equal(StateMachine::MAX_STEPS , the_walk.transitions.length , "Check Walks to the maximum in a loop.")
  217. # Check limited length walk
  218. the_walk = sm.random_walk("STATEA",5)
  219. assert_equal(5 , the_walk.transitions.length , "Check Walks to the given length in a loop.")
  220. # Check that exception raised if walk length is daft (<=2)
  221. assert_raises RuntimeError do
  222. the_walk = sm.random_walk("STATEA" , 2)
  223. end # Assert Raises
  224. end # end
  225. # Random Walk
  226. # Sanity check % stats returned
  227. # Also do a specific check...
  228. #
  229. def test_random_walk_muliple
  230. @valid_csv_files.each do |csv_file|
  231. smc = StateModelCreator.new(true)
  232. sm = smc.load_table(csv_file)
  233. the_walk = sm.random_walk("STATEA")
  234. # Puts it, hope there is no exceptions
  235. puts the_walk
  236. # Generic checks on coverage stats
  237. check_coverage_stats(the_walk)
  238. end # end loop over csvs
  239. # Specific checks for coverage on this model
  240. smc = StateModelCreator.new
  241. sm = smc.load_table(TEST10_CSV)
  242. a_walk = sm.random_walk("STATEA")
  243. assert_equal(80 , a_walk.state_coverage , "Check State coverage is 80% for TEST10_CSV")
  244. assert_equal(75 , a_walk.transition_coverage , "Check Transition coverage is 75% for TEST10_CSV")
  245. end # end test
  246. # Walk-State and transition coverage
  247. #
  248. # Sanity check that the values are within realistic constraints
  249. #
  250. def check_coverage_stats(the_walk)
  251. # Check coverage stats are sane.
  252. assert(the_walk.state_coverage <= 100 , "Check State coverage is <=100")
  253. assert(the_walk.state_coverage > 0 , "Check State coverage is >0")
  254. assert(the_walk.transition_coverage <= 100 , "Check transition coverage is <=100")
  255. assert(the_walk.transition_coverage > 0 , "Check transition coverage is >0")
  256. end # end coverage stats
  257. # Random Walk
  258. #
  259. # Check that the Random Walker follows a simple straight line graph ok.
  260. # Path is detirministic as there is only one option for progression on each node.
  261. #
  262. def test_random_walk_many_straight_steps
  263. smc = StateModelCreator.new
  264. sm = smc.load_table(TEST5_CSV)
  265. the_walk = sm.random_walk("STATEA")
  266. puts "test_random_walk_many_straight_steps"
  267. puts the_walk
  268. # Check that the transitions are found (when only 1 choice) and ordered correctly
  269. trans = the_walk.transitions
  270. assert(trans[0].start_state=="STATEA" , "State A is 1st start state")
  271. assert(trans[0].end_state=="STATEB" , "State B is 1st end state")
  272. assert(trans[1].start_state=="STATEB" , "State B is 2nd start state")
  273. assert(trans[1].end_state=="STATEC" , "State C is 2nd end state")
  274. assert(trans[2].start_state=="STATEC" , "State C is 3rd start state")
  275. assert(trans[2].end_state=="STATED" , "State D is 3rd end state")
  276. assert(trans[3].start_state=="STATED" , "State D is 4th start state")
  277. assert(trans[3].end_state=="STATEE" , "State E is 4th end state")
  278. assert(trans[4].start_state=="STATEE" , "State E is 5th start state")
  279. assert(trans[4].end_state=="STATEF" , "State F is 5th end state")
  280. assert(trans[5].start_state=="STATEF" , "State F is 6th start state")
  281. assert(trans[5].end_state=="STATEG" , "State G is 6th end state")
  282. assert(trans[6].start_state=="STATEG" , "State G is 7th start state")
  283. assert(trans[6].end_state=="STATEH" , "State H is 7th end state")
  284. end # end test
  285. # General: Death tests
  286. # Ensure the system exits with Runtime Errors when blatantly bad data is provided.
  287. #
  288. def test_random_walk_file_issues
  289. # CSV file with just a header
  290. assert_raises RuntimeError do
  291. smc = StateModelCreator.new
  292. smc.load_table(@path + TEST2_1LINE_CSV)
  293. end # Assert Raises
  294. # A CSV File with nothing in it.
  295. assert_raises RuntimeError do
  296. smc = StateModelCreator.new
  297. smc.load_table(@path + TEST3_0LINE_CSV)
  298. end # Assert Raises
  299. end # end test
  300. # Drive Using
  301. # drive_using can directly control a sut driver object and follow a given walk.
  302. # The 'walk' is passed the 'driver', the walk then executes any transitions contained in the walk.
  303. # Assertions are placed in the driver and therefore executed as part of this process.
  304. def test_model_driver_simple_csv
  305. smc = StateModelCreator.new # Create model
  306. system_model = smc.load_table(@path + TEST10_CSV)
  307. model_walk = system_model.random_walk("STATEA") # Create walk starting at ...
  308. sut_driver = EgTestDriver.new # Instantiate your target system's test driver
  309. model_walk.drive_using sut_driver # Apply the 'Walk' to your driver code.
  310. # Ensure correct transitions were followed, in correct order
  311. assert_equal(sut_driver.transitions_travelled , model_walk.transitions)
  312. # Compile list of states that should get tested in the SUT
  313. models_states=Array.new
  314. model_walk.transitions.each do |a_transition|
  315. if models_states.length==0
  316. models_states.push a_transition.start_state
  317. end # if first state
  318. models_states.push a_transition.end_state
  319. end # end do trans
  320. assert_equal(sut_driver.states_tested , models_states)
  321. end # end driver tests
  322. def test_model_driver_simple_dsl
  323. system_model = build_dsl_10 # Create model
  324. model_walk = system_model.random_walk(:STATEA) # Create walk starting at ...
  325. sut_driver = EgTestDriver.new(true) # Instantiate your target system's test driver
  326. model_walk.drive_using sut_driver # Apply the 'Walk' to your driver code.
  327. # Ensure correct transitions were followed, in correct order
  328. assert_equal(sut_driver.transitions_travelled , model_walk.transitions)
  329. # Compile list of states that should get tested in the SUT
  330. models_states=Array.new
  331. model_walk.transitions.each do |a_transition|
  332. if models_states.length==0
  333. models_states.push a_transition.start_state
  334. end # if first state
  335. models_states.push a_transition.end_state
  336. end # end do trans
  337. assert_equal(sut_driver.states_tested , models_states)
  338. end # end driver tests
  339. # Drive Using
  340. # Just check a runtime error is raised when a string is passsed in
  341. # instead of a valid driver. A common error.
  342. # To allow any old code /driver to be used though - means i
  343. # can not be very specific about what i allow.
  344. def test_model_driver_messy
  345. smc = StateModelCreator.new # Create model
  346. system_model = smc.load_table(TEST10_CSV)
  347. model_walk = system_model.random_walk("STATEA") # Create walk starting at ...
  348. sut_driver = String.new # Instantiate bogus test driver
  349. assert_raises RuntimeError do
  350. model_walk.drive_using sut_driver # Ensure exception occurs when not real driver
  351. end # end assert raises
  352. end # end messy driver tests
  353. # Unit Tests for a method that examines adjacency matrix and returns the transitions
  354. #
  355. def test_extract_valid_transitions
  356. @valid_csv_files.each_index do |csv_file_index|
  357. smc = StateModelCreator.new # Create model
  358. system_model = smc.load_table(@valid_csv_files[csv_file_index])
  359. # Test that correct number of transitions were extracted
  360. assert_equal(@valid_csv_files_transitions[csv_file_index], system_model.extract_valid_transitions.length, "Check for correct number of transitions: #{@valid_csv_files[csv_file_index]}")
  361. end # end csv files
  362. # Check specific example has correct values set
  363. # Manually create 2 transitions, These match those in TEST1_CSV
  364. smc = StateModelCreator.new # Create model
  365. system_model = smc.load_table(TEST1_CSV)
  366. correct_transition1=TransitionHolder.new("STATEA","action1","STATEB")
  367. correct_transition2=TransitionHolder.new("STATEB","action2","STATEA")
  368. # Obtain our extracted transitions
  369. sut_trans = system_model.extract_valid_transitions
  370. sut_transitionX=sut_trans[0]
  371. # Compare X transitions with each manually created one.
  372. # We can not detirmine what order they will be extracted.
  373. # Therefore we have to check that one pair matches and the other does not.
  374. pairX1,pairX2 = false , false
  375. if sut_transitionX == correct_transition1
  376. pairX1=true
  377. end # end if
  378. if sut_transitionX == correct_transition2
  379. pairX2=true
  380. end # end if
  381. puts "Pair checks:#{pairX1},#{pairX2}"
  382. assert(pairX1||pairX2, "A extracted transition should match -one- of these manually created ones")
  383. assert(pairX1!=pairX2, "Check that extracted transition does not match both manual ones (this is a bit redundant).")
  384. end # end test mthod
  385. # DSL Stuff
  386. #
  387. def test_state_simple_transition_define
  388. myfsmc = StateModelCreator.new
  389. myfsmc.define_action :action1 do
  390. puts "stuff"
  391. end # end add action
  392. # Create a transition
  393. myfsmc.attach_transition(:STATEA ,:action1 ,:STATEB)
  394. # Check states were added
  395. assert(myfsmc.states.include?(:STATEA) , "Check STATEA added")
  396. assert(myfsmc.states.include?(:STATEB) , "Check STATEB added")
  397. end # end test
  398. # If an action isn't there should throw an exception
  399. #
  400. def test_state_simple_transition_missing
  401. myfsmc = StateModelCreator.new
  402. assert_raises RuntimeError do
  403. returned_obj = myfsmc.attach_transition(:state1 , :action3 , :state2)
  404. end # end assert
  405. end # end state test
  406. def test_action_simple_action_define
  407. myfsmc = StateModelCreator.new
  408. myfsmc.define_action :action1 do
  409. puts "stuff"
  410. end # end add action
  411. assert_equal(:action1 , myfsmc.actions[0], "Check name of loose action")
  412. myfsmc.attach_transition(:STATEA, :action1 , :STATEB)
  413. # Check method there by just calling it...
  414. sm = myfsmc.state_machine
  415. sm.state=:STATEA
  416. assert_equal(:STATEB , sm.action1 , "Check new state is STATEB, using return from StateMachine#action_name")
  417. assert_equal(:STATEB , sm.state , "Check new state is STATEB, using StateMachine#state")
  418. # Hopefully that did not throw an exception.
  419. # Lets add another
  420. myfsmc.define_action :action2
  421. myfsmc.attach_transition(:STATEA, :action2 , :STATEB)
  422. sm = myfsmc.state_machine
  423. sm.state=:STATEA
  424. # Check they have both been added (low rent test)
  425. assert(myfsmc.actions.length==2, "Check 2 entries added to actions list")
  426. the_state_machine = myfsmc.state_machine
  427. the_state_machine.action2
  428. end # end test
  429. def test_random_walk_simple_dsl
  430. smc = StateModelCreator.new
  431. smc.define_action :action1
  432. smc.define_action :action2
  433. smc.attach_transition(:STATEA,:action1,:STATEB)
  434. smc.attach_transition(:STATEB,:action2,:STATEA)
  435. sm = smc.state_machine
  436. assert_equal(2 , sm.states_store.length , "Check for 2 states")
  437. # Check standard length walk
  438. the_walk = sm.random_walk(:STATEA)
  439. assert_equal(Walk.new.class , the_walk.class , "Check random walk returns Walk instance" )
  440. assert_equal(StateMachine::MAX_STEPS , the_walk.transitions.length , "Check Walks to the maximum in a loop.")
  441. # Check limited length walk
  442. the_walk = sm.random_walk(:STATEA,5)
  443. assert_equal(5 , the_walk.transitions.length , "Check Walks to the given length in a loop.")
  444. # Check that exception raised if walk length is daft (<=2)
  445. assert_raises RuntimeError do
  446. the_walk = sm.random_walk(:STATEA , 2)
  447. end # Assert Raises
  448. end # end
  449. def test_steps_simple_dsl
  450. smc = StateModelCreator.new
  451. smc.define_action :action1
  452. smc.define_action :action2
  453. smc.attach_transition(:STATEA,:action1,:STATEB)
  454. smc.attach_transition(:STATEB,:action2,:STATEA)
  455. system_model = smc.state_machine
  456. system_model.state = :STATEA
  457. new_state = system_model.action1
  458. assert(new_state==:STATEB, "Check action1 transitioned to STATEB - returned value")
  459. assert(system_model.state==:STATEB, "Check action1 transitioned to STATEB - state value")
  460. end # end def
  461. def teardown
  462. end # end teardown/clearup
  463. end # end class