stamina 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gemtest +0 -0
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +33 -0
  5. data/LICENCE.md +22 -0
  6. data/Manifest.txt +16 -0
  7. data/README.md +78 -0
  8. data/Rakefile +23 -0
  9. data/bin/adl2dot +12 -0
  10. data/bin/classify +12 -0
  11. data/bin/redblue +12 -0
  12. data/bin/rpni +12 -0
  13. data/example/adl/automaton.adl +49 -0
  14. data/example/adl/sample.adl +53 -0
  15. data/example/basic/characteristic_sample.adl +32 -0
  16. data/example/basic/target.adl +9 -0
  17. data/example/competition/31_test.adl +1500 -0
  18. data/example/competition/31_training.adl +1759 -0
  19. data/lib/stamina.rb +19 -0
  20. data/lib/stamina/adl.rb +298 -0
  21. data/lib/stamina/automaton.rb +1237 -0
  22. data/lib/stamina/automaton/walking.rb +336 -0
  23. data/lib/stamina/classifier.rb +37 -0
  24. data/lib/stamina/command/adl2dot_command.rb +73 -0
  25. data/lib/stamina/command/classify_command.rb +57 -0
  26. data/lib/stamina/command/redblue_command.rb +58 -0
  27. data/lib/stamina/command/rpni_command.rb +58 -0
  28. data/lib/stamina/command/stamina_command.rb +79 -0
  29. data/lib/stamina/errors.rb +20 -0
  30. data/lib/stamina/induction/commons.rb +170 -0
  31. data/lib/stamina/induction/redblue.rb +264 -0
  32. data/lib/stamina/induction/rpni.rb +188 -0
  33. data/lib/stamina/induction/union_find.rb +377 -0
  34. data/lib/stamina/input_string.rb +123 -0
  35. data/lib/stamina/loader.rb +0 -0
  36. data/lib/stamina/markable.rb +42 -0
  37. data/lib/stamina/sample.rb +190 -0
  38. data/lib/stamina/version.rb +14 -0
  39. data/stamina.gemspec +190 -0
  40. data/stamina.noespec +35 -0
  41. data/tasks/debug_mail.rake +78 -0
  42. data/tasks/debug_mail.txt +13 -0
  43. data/tasks/gem.rake +68 -0
  44. data/tasks/spec_test.rake +79 -0
  45. data/tasks/unit_test.rake +77 -0
  46. data/tasks/yard.rake +51 -0
  47. data/test/stamina/adl_test.rb +491 -0
  48. data/test/stamina/automaton_additional_test.rb +190 -0
  49. data/test/stamina/automaton_classifier_test.rb +155 -0
  50. data/test/stamina/automaton_test.rb +1092 -0
  51. data/test/stamina/automaton_to_dot_test.rb +64 -0
  52. data/test/stamina/automaton_walking_test.rb +206 -0
  53. data/test/stamina/exit.rb +3 -0
  54. data/test/stamina/induction/induction_test.rb +70 -0
  55. data/test/stamina/induction/redblue_mergesamestatebug_expected.adl +19 -0
  56. data/test/stamina/induction/redblue_mergesamestatebug_pta.dot +64 -0
  57. data/test/stamina/induction/redblue_mergesamestatebug_sample.adl +9 -0
  58. data/test/stamina/induction/redblue_test.rb +83 -0
  59. data/test/stamina/induction/redblue_universal_expected.adl +4 -0
  60. data/test/stamina/induction/redblue_universal_sample.adl +5 -0
  61. data/test/stamina/induction/rpni_inria_expected.adl +7 -0
  62. data/test/stamina/induction/rpni_inria_sample.adl +9 -0
  63. data/test/stamina/induction/rpni_test.rb +129 -0
  64. data/test/stamina/induction/rpni_test_pta.dot +22 -0
  65. data/test/stamina/induction/rpni_universal_expected.adl +4 -0
  66. data/test/stamina/induction/rpni_universal_sample.adl +4 -0
  67. data/test/stamina/induction/union_find_test.rb +124 -0
  68. data/test/stamina/input_string_test.rb +323 -0
  69. data/test/stamina/markable_test.rb +70 -0
  70. data/test/stamina/randdfa.adl +66 -0
  71. data/test/stamina/sample.adl +4 -0
  72. data/test/stamina/sample_classify_test.rb +149 -0
  73. data/test/stamina/sample_test.rb +218 -0
  74. data/test/stamina/small_dfa.dot +16 -0
  75. data/test/stamina/small_dfa.gif +0 -0
  76. data/test/stamina/small_nfa.dot +18 -0
  77. data/test/stamina/small_nfa.gif +0 -0
  78. data/test/stamina/stamina_test.rb +69 -0
  79. data/test/test_all.rb +7 -0
  80. metadata +279 -0
@@ -0,0 +1,35 @@
1
+ # Noe template for ruby gem libraries (https://github.com/blambeau/noe) - short version
2
+ # Run 'noe show-spec' and 'noe help show-spec' for additional details.
3
+ template-info:
4
+ name: "ruby"
5
+ version: 1.3.0
6
+ variables:
7
+ lower:
8
+ stamina
9
+ upper:
10
+ Stamina
11
+ version:
12
+ 0.3.0
13
+ summary: |-
14
+ Automaton and Regular Inference Toolkit
15
+ description: |-
16
+ Stamina is an automaton and regular inference toolkit initially developped for the baseline
17
+ of the Stamina Competition (stamina.chefbe.net).
18
+ authors:
19
+ - name: Bernard Lambeau
20
+ email: blambeau@gmail.com
21
+ links:
22
+ - http://stamina.chefbe.net/
23
+ - http://github.com/blambeau/stamina
24
+ dependencies:
25
+ # Rake is required for developers, as usual
26
+ - {name: rake, version: "~> 0.8.7", groups: [development]}
27
+ # Bundler is required for developers and is used by the Rakefile
28
+ - {name: bundler, version: "~> 1.0", groups: [development]}
29
+ # RSpec is required to run 'rake spec'. See tasks/spec.rake
30
+ - {name: rspec, version: "~> 2.4.0", groups: [development]}
31
+ # YARD and BlueCloth are required to run 'rake yard'. See tasks/yard.rake
32
+ - {name: yard, version: "~> 0.6.4", groups: [development]}
33
+ - {name: bluecloth, version: "~> 2.0.9", groups: [development]}
34
+ # wlang is required to run 'rake debug_mail'. See tasks/debug_mail.rake
35
+ - {name: wlang, version: "~> 0.10.1", groups: [development]}
@@ -0,0 +1,78 @@
1
+ # Installs a rake task for debuging the announcement mail.
2
+ #
3
+ # This file installs the 'rake debug_mail' that flushes an announcement mail
4
+ # for your library on the standard output. It is automatically generated
5
+ # by Noe from your .noespec file, and should therefore be configured there,
6
+ # under the variables/rake_tasks/debug_mail entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # debug_mail:
11
+ # rx_changelog_sections: /^#/
12
+ # nb_changelog_sections: 1
13
+ # ...
14
+ #
15
+ # If you have specific needs requiring manual intervention on this file,
16
+ # don't forget to set safe-override to false in your noe specification:
17
+ #
18
+ # template-info:
19
+ # manifest:
20
+ # tasks/debug_mail.rake:
21
+ # safe-override: false
22
+ #
23
+ # The mail template used can be found in debug_mail.txt. That file may be
24
+ # changed to tune the mail you want to send. If you do so, don't forget to
25
+ # add a manifest entry in your .noespec file to avoid overriding you
26
+ # changes. The mail template uses wlang, with parentheses for block
27
+ # delimiters.
28
+ #
29
+ # template-info:
30
+ # manifest:
31
+ # tasks/debug_mail.txt:
32
+ # safe-override: false
33
+ #
34
+ begin
35
+ require 'wlang'
36
+ require 'yaml'
37
+
38
+ desc "Debug the release announcement mail"
39
+ task :debug_mail do
40
+ # Check that a .noespec file exists
41
+ noespec_file = File.expand_path('../../stamina.noespec', __FILE__)
42
+ unless File.exists?(noespec_file)
43
+ raise "Unable to find .noespec project file, sorry."
44
+ end
45
+
46
+ # Load it as well as variables and options
47
+ noespec = YAML::load(File.read(noespec_file))
48
+ vars = noespec['variables'] || {}
49
+
50
+ # Changes are taken from CHANGELOG
51
+ logs = Dir[File.expand_path("../../CHANGELOG.*", __FILE__)]
52
+ unless logs.size == 1
53
+ abort "Unable to find a changelog file"
54
+ end
55
+
56
+ # Load interesting changesets
57
+ changes, end_found = [], 0
58
+ File.readlines(logs.first).select{|line|
59
+ if line =~ /^#/
60
+ break if end_found >= 1
61
+ end_found += 1
62
+ end
63
+ changes << line
64
+ }
65
+ vars['changes'] = changes.join
66
+
67
+ # WLang template
68
+ template = File.expand_path('../debug_mail.txt', __FILE__)
69
+
70
+ # Let's go!
71
+ $stdout << WLang::file_instantiate(template, vars, "wlang/active-text")
72
+ end
73
+
74
+ rescue LoadError
75
+ task :debug_mail do
76
+ abort "wlang is not available. Try 'gem install wlang'"
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ Subject: [ANN] !{lower} !{version} Released
2
+
3
+ !{lower} version !{version} has been released!
4
+
5
+ !{summary}
6
+
7
+ *{links as l}{* <!{l}>}{!{"\n"}}
8
+
9
+ !{description}
10
+
11
+ Changes:
12
+
13
+ !{changes}
@@ -0,0 +1,68 @@
1
+ # Installs rake tasks for gemming and packaging
2
+ #
3
+ # This file installs the 'rake package', 'rake gem' tasks and associates
4
+ # (clobber_package, repackage, ...). It is automatically generated by Noe
5
+ # from your .noespec file, and should therefore be configured there, under
6
+ # the variables/rake_tasks/gem entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # gem:
11
+ # package_dir: pkg
12
+ # need_tar: false
13
+ # need_tar_gz: false
14
+ # need_tar_bz2: false
15
+ # need_zip: false
16
+ # ...
17
+ #
18
+ # If you have specific needs requiring manual intervention on this file,
19
+ # don't forget to set safe-override to false in your noe specification:
20
+ #
21
+ # template-info:
22
+ # manifest:
23
+ # tasks/gem.rake:
24
+ # safe-override: false
25
+ #
26
+ begin
27
+ require 'rubygems/package_task'
28
+ Gem::PackageTask.new($gemspec) do |t|
29
+
30
+ # Name of the package
31
+ t.name = $gemspec.name
32
+
33
+ # Version of the package
34
+ t.version = $gemspec.version
35
+
36
+ # Directory used to store the package files
37
+ t.package_dir = "pkg"
38
+
39
+ # True if a gzipped tar file (tgz) should be produced
40
+ t.need_tar = false
41
+
42
+ # True if a gzipped tar file (tar.gz) should be produced
43
+ t.need_tar_gz = false
44
+
45
+ # True if a bzip2'd tar file (tar.bz2) should be produced
46
+ t.need_tar_bz2 = false
47
+
48
+ # True if a zip file should be produced (default is false)
49
+ t.need_zip = false
50
+
51
+ # List of files to be included in the package.
52
+ t.package_files = $gemspec.files
53
+
54
+ # Tar command for gzipped or bzip2ed archives.
55
+ t.tar_command = "tar"
56
+
57
+ # Zip command for zipped archives.
58
+ t.zip_command = "zip"
59
+
60
+ end
61
+ rescue LoadError
62
+ task :gem do
63
+ abort 'rubygems/package_task is not available. You should verify your rubygems installation'
64
+ end
65
+ task :package do
66
+ abort 'rubygems/package_task is not available. You should verify your rubygems installation'
67
+ end
68
+ end
@@ -0,0 +1,79 @@
1
+ # Installs a rake task for for running examples written using rspec.
2
+ #
3
+ # This file installs the 'rake spec_test' (aliased as 'rake spec') as well as
4
+ # extends 'rake test' to run spec tests, if any. It is automatically generated
5
+ # by Noe from your .noespec file, and should therefore be configured there,
6
+ # under the variables/rake_tasks/spec_test entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # spec_test:
11
+ # pattern: spec/**/*_spec.rb
12
+ # verbose: true
13
+ # rspec_opts: [--color, --backtrace]
14
+ # ...
15
+ #
16
+ # If you have specific needs requiring manual intervention on this file,
17
+ # don't forget to set safe-override to false in your noe specification:
18
+ #
19
+ # template-info:
20
+ # manifest:
21
+ # tasks/spec_test.rake:
22
+ # safe-override: false
23
+ #
24
+ # This file has been written to conform to RSpec v2.4.0. More information about
25
+ # rspec and options of the rake task defined below can be found on
26
+ # http://relishapp.com/rspec
27
+ #
28
+ begin
29
+ require "rspec/core/rake_task"
30
+ desc "Run RSpec code examples"
31
+ RSpec::Core::RakeTask.new(:spec_test) do |t|
32
+ # Glob pattern to match files.
33
+ t.pattern = "spec/**/*_spec.rb"
34
+
35
+ # By default, if there is a Gemfile, the generated command will include
36
+ # 'bundle exec'. Set this to true to ignore the presence of a Gemfile,
37
+ # and not add 'bundle exec' to the command.
38
+ t.skip_bundler = false
39
+
40
+ # Name of Gemfile to use
41
+ t.gemfile = "Gemfile"
42
+
43
+ # Whether or not to fail Rake when an error occurs (typically when
44
+ # examples fail).
45
+ t.fail_on_error = true
46
+
47
+ # A message to print to stderr when there are failures.
48
+ t.failure_message = nil
49
+
50
+ # Use verbose output. If this is set to true, the task will print the
51
+ # executed spec command to stdout.
52
+ t.verbose = true
53
+
54
+ # Use rcov for code coverage?
55
+ t.rcov = false
56
+
57
+ # Path to rcov.
58
+ t.rcov_path = "rcov"
59
+
60
+ # Command line options to pass to rcov. See 'rcov --help' about this
61
+ t.rcov_opts = []
62
+
63
+ # Command line options to pass to ruby. See 'ruby --help' about this
64
+ t.ruby_opts = []
65
+
66
+ # Path to rspec
67
+ t.rspec_path = "rspec"
68
+
69
+ # Command line options to pass to rspec. See 'rspec --help' about this
70
+ t.rspec_opts = ["--color", "--backtrace"]
71
+ end
72
+ rescue LoadError => ex
73
+ task :spec_test do
74
+ abort 'rspec is not available. In order to run spec, you must: gem install rspec'
75
+ end
76
+ ensure
77
+ task :spec => [:spec_test]
78
+ task :test => [:spec_test]
79
+ end
@@ -0,0 +1,77 @@
1
+ # Installs a rake task for for running unit tests.
2
+ #
3
+ # This file installs the 'rake unit_test' and extends 'rake test' to run unit
4
+ # tests, if any. It is automatically generated by Noe from your .noespec file,
5
+ # and should therefore be configured there, under the variables/rake_tasks/unit_test
6
+ # entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # unit_test:
11
+ # pattern: test/test*.rb
12
+ # verbose: false
13
+ # warning: false
14
+ # ...
15
+ #
16
+ # If you have specific needs requiring manual intervention on this file,
17
+ # don't forget to set safe-override to false in your noe specification:
18
+ #
19
+ # template-info:
20
+ # manifest:
21
+ # tasks/unit_test.rake:
22
+ # safe-override: false
23
+ #
24
+ # More info about the TestTask and its options can be found on
25
+ # http://rake.rubyforge.org/classes/Rake/TestTask.html
26
+ #
27
+ begin
28
+ require 'rake/testtask'
29
+ desc "Run unit tests"
30
+ Rake::TestTask.new(:unit_test) do |t|
31
+
32
+ # List of directories to added to $LOAD_PATH before running the
33
+ # tests. (default is 'lib')
34
+ t.libs = ["lib"]
35
+
36
+ # True if verbose test output desired. (default is false)
37
+ t.verbose = false
38
+
39
+ # Test options passed to the test suite. An explicit TESTOPTS=opts
40
+ # on the command line will override this. (default is NONE)
41
+ t.options = nil
42
+
43
+ # Request that the tests be run with the warning flag set.
44
+ # E.g. warning=true implies "ruby -w" used to run the tests.
45
+ t.warning = false
46
+
47
+ # Glob pattern to match test files. (default is 'test/test*.rb')
48
+ t.pattern = "test/test*.rb"
49
+
50
+ # Style of test loader to use. Options are:
51
+ #
52
+ # * :rake -- Rake provided test loading script (default).
53
+ # * :testrb -- Ruby provided test loading script.
54
+ # * :direct -- Load tests using command line loader.
55
+ #
56
+ t.loader = :rake
57
+
58
+ # Array of commandline options to pass to ruby when running test
59
+ # loader.
60
+ t.ruby_opts = []
61
+
62
+ # Explicitly define the list of test files to be included in a
63
+ # test. +list+ is expected to be an array of file names (a
64
+ # FileList is acceptable). If both +pattern+ and +test_files+ are
65
+ # used, then the list of test files is the union of the two.
66
+ t.test_files = nil
67
+
68
+ end
69
+ rescue LoadError => ex
70
+ task :unit_test do
71
+ abort 'rspec is not available. In order to run spec, you must: gem install rspec'
72
+ end
73
+ ensure
74
+ desc "Run all tests"
75
+ task :test => [:unit_test]
76
+ end
77
+
@@ -0,0 +1,51 @@
1
+ # Installs a rake task to generate API documentation using yard.
2
+ #
3
+ # This file installs the 'rake yard' task. It is automatically generated by Noe from
4
+ # your .noespec file, and should therefore be configured there, under the
5
+ # variables/rake_tasks/yard entry, as illustrated below:
6
+ #
7
+ # variables:
8
+ # rake_tasks:
9
+ # yard:
10
+ # files: lib/**/*.rb
11
+ # options: []
12
+ # ...
13
+ #
14
+ # If you have specific needs requiring manual intervention on this file,
15
+ # don't forget to set safe-override to false in your noe specification:
16
+ #
17
+ # template-info:
18
+ # manifest:
19
+ # tasks/yard.rake:
20
+ # safe-override: false
21
+ #
22
+ # This file has been written to conform to yard v0.6.4. More information about
23
+ # yard and the rake task installed below can be found on http://yardoc.org/
24
+ #
25
+ begin
26
+ require "yard"
27
+ desc "Generate yard documentation"
28
+ YARD::Rake::YardocTask.new(:yard) do |t|
29
+ # Array of options passed to yardoc commandline. See 'yardoc --help' about this
30
+ t.options = ["--output-dir", "doc/api", "-", "README.md", "CHANGELOG.md", "LICENCE.md"]
31
+
32
+ # Array of ruby source files (and any extra documentation files
33
+ # separated by '-')
34
+ t.files = ["lib/**/*.rb"]
35
+
36
+ # A proc to call before running the task
37
+ # t.before = proc{ }
38
+
39
+ # A proc to call after running the task
40
+ # r.after = proc{ }
41
+
42
+ # An optional lambda to run against all objects being generated.
43
+ # Any object that the lambda returns false for will be excluded
44
+ # from documentation.
45
+ # t.verifier = lambda{|obj| true}
46
+ end
47
+ rescue LoadError
48
+ task :yard do
49
+ abort 'yard is not available. In order to run yard, you must: gem install yard'
50
+ end
51
+ end
@@ -0,0 +1,491 @@
1
+ require 'test/unit'
2
+ require 'stamina'
3
+ module Stamina
4
+
5
+ # Tests ADL parser
6
+ class ADLTest < Test::Unit::TestCase
7
+
8
+ # Tests ADL#parse on a valid dfa
9
+ def test_can_parse_valid_empty_dfa
10
+ fa = ADL::parse_automaton <<-AUTOMATON
11
+ 1 0
12
+ 0 true false
13
+ AUTOMATON
14
+ assert_equal(1, fa.state_count)
15
+ assert_equal(0, fa.edge_count)
16
+ assert_equal(true, fa.states[0].initial?)
17
+ assert_equal(false, fa.states[0].accepting?)
18
+ assert_equal(true, fa.deterministic?)
19
+ assert_equal(false, fa.accepts?('+'))
20
+ assert_equal(false, fa.accepts?('+ a'))
21
+ end
22
+
23
+ # Tests ADL#parse on a valid dfa
24
+ def test_can_parse_valid_small_dfa
25
+ fa = ADL::parse_automaton <<-AUTOMATON
26
+ 3 4
27
+ 0 true false
28
+ 1 false false
29
+ 2 false true
30
+ 0 1 a
31
+ 1 2 b
32
+ 2 2 a
33
+ 2 1 b
34
+ AUTOMATON
35
+ assert_equal(3, fa.state_count)
36
+ assert_equal(4, fa.edge_count)
37
+ fa.each_state {|s| assert_equal(s.index==0, s.initial?)}
38
+ fa.each_state {|s| assert_equal(s.index==2, s.accepting?)}
39
+ assert_equal(false, fa.accepts?('+'))
40
+ assert_equal(false, fa.accepts?('+ a'))
41
+ assert_equal(true, fa.accepts?('+ a b'))
42
+ assert_equal(true, fa.accepts?('+ a b a'))
43
+ assert_equal(false, fa.accepts?('+ a b a b'))
44
+ assert_equal(true, fa.accepts?('+ a b a a a'))
45
+ assert_equal(true, fa.accepts?('+ a b a a a b b a'))
46
+ assert_equal(true, fa.accepts?('+ a b a a a b b a a a'))
47
+ assert_equal(true, fa.accepts?('+ a b a a a b b a a a b b a'))
48
+ end
49
+
50
+ # Tests that ADL#parse detects a missing state
51
+ def test_detect_missing_header
52
+ assert_raise(ADL::ParseError) do
53
+ ADL::parse_automaton <<-AUTOMATON
54
+ 0 true false
55
+ 1 false false
56
+ 0 1 a
57
+ 1 2 b
58
+ 2 2 a
59
+ 2 1 b
60
+ AUTOMATON
61
+ end
62
+ assert_raise(ADL::ParseError) do
63
+ ADL::parse_automaton <<-AUTOMATON
64
+ # 3 4
65
+ 0 true false
66
+ 1 false false
67
+ 0 1 a
68
+ 1 2 b
69
+ 2 2 a
70
+ 2 1 b
71
+ AUTOMATON
72
+ end
73
+ end
74
+
75
+ # Tests that ADL#parse detects a missing state
76
+ def test_detect_missing_state
77
+ assert_raise(ADL::ParseError) do
78
+ ADL::parse_automaton <<-AUTOMATON
79
+ 3 4
80
+ 0 true false
81
+ 1 false false
82
+ AUTOMATON
83
+ end
84
+ assert_raise(ADL::ParseError) do
85
+ ADL::parse_automaton <<-AUTOMATON
86
+ 3 4
87
+ 0 true false
88
+ 1 false false
89
+ 0 1 a
90
+ 1 2 b
91
+ 2 2 a
92
+ 2 1 b
93
+ AUTOMATON
94
+ end
95
+ assert_raise(ADL::ParseError) do
96
+ ADL::parse_automaton <<-AUTOMATON
97
+ 3 4
98
+ 0 true false
99
+ 1 false false
100
+ # 2 false true
101
+ 0 1 a
102
+ 1 2 b
103
+ 2 2 a
104
+ 2 1 b
105
+ AUTOMATON
106
+ end
107
+ end
108
+
109
+ # Tests that ADL#parse detects a missing edge
110
+ def test_detect_missing_edge
111
+ assert_raise(ADL::ParseError) do
112
+ ADL::parse_automaton <<-AUTOMATON
113
+ 3 4
114
+ 0 true false
115
+ 1 false false
116
+ 2 false true
117
+ 0 1 a
118
+ 2 2 a
119
+ 2 1 b
120
+ AUTOMATON
121
+ end
122
+ assert_raise(ADL::ParseError) do
123
+ ADL::parse_automaton <<-AUTOMATON
124
+ 3 4
125
+ 0 true false
126
+ 1 false false
127
+ 2 false true
128
+ 0 1 a
129
+ 1 2 b
130
+ 2 2 a
131
+ # 2 1 b
132
+ AUTOMATON
133
+ end
134
+ end
135
+
136
+ # Tests that ADL#parse detects a missing edge
137
+ def test_detect_trailing_data
138
+ assert_raise(ADL::ParseError) do
139
+ fa = ADL::parse_automaton <<-AUTOMATON
140
+ 1 0
141
+ 0 true false
142
+ trailing here
143
+ AUTOMATON
144
+ end
145
+ end
146
+
147
+ # Tests that ADL#parse detects a missing edge
148
+ def test_allows_comments_and_white_lines
149
+ fa = nil
150
+ assert_nothing_raised(ADL::ParseError) do
151
+ fa = ADL::parse_automaton <<-AUTOMATON
152
+
153
+ # a header is always allowed,
154
+ # with empty lines as well
155
+ #
156
+ 3 4
157
+
158
+ # state definitions may be introduced...
159
+ 0 true false
160
+ 1 false false
161
+ # and perturbated
162
+ 2 false true
163
+ 0 1 a
164
+
165
+ # edge introduction may be misplaced
166
+ 1 2 b
167
+ 2 2 a
168
+
169
+ 2 1 b
170
+
171
+ # and end of file may contain documentation as well
172
+ # as empty lines:
173
+
174
+ AUTOMATON
175
+ end
176
+ assert_equal(3, fa.state_count)
177
+ assert_equal(4, fa.edge_count)
178
+ fa.each_state {|s| assert_equal(s.index==0, s.initial?)}
179
+ fa.each_state {|s| assert_equal(s.index==2, s.accepting?)}
180
+ assert_equal(false, fa.accepts?('+'))
181
+ assert_equal(false, fa.accepts?('+ a'))
182
+ assert_equal(true, fa.accepts?('+ a b'))
183
+ assert_equal(true, fa.accepts?('+ a b a'))
184
+ assert_equal(false, fa.accepts?('+ a b a b'))
185
+ assert_equal(true, fa.accepts?('+ a b a a a'))
186
+ assert_equal(true, fa.accepts?('+ a b a a a b b a'))
187
+ assert_equal(true, fa.accepts?('+ a b a a a b b a a a'))
188
+ assert_equal(true, fa.accepts?('+ a b a a a b b a a a b b a'))
189
+ end
190
+
191
+ # Tests ADL::parse on the documentation example
192
+ def test_valid_adl_automaton_example
193
+ fa = nil
194
+ assert_nothing_raised(ADL::ParseError) do
195
+ here = File.dirname(__FILE__)
196
+ automaton_adl = File.join(here, '..', '..', 'example', 'adl', 'automaton.adl')
197
+ fa = ADL::parse_automaton_file(automaton_adl)
198
+ end # assert_nothing_raised
199
+ assert_equal(5, fa.state_count)
200
+ assert_equal(6, fa.edge_count)
201
+ assert_equal(true, fa.parses?('? hello w o r l d'))
202
+ assert_equal(false, fa.accepts?('? hello w o r l d'))
203
+ assert_equal(true, fa.rejects?('? hello w o r l d'))
204
+ assert_equal(true, fa.accepts?('? hello'))
205
+ assert_equal(false, fa.accepts?('? hello w'))
206
+ assert_equal(true, fa.accepts?('? hello w o'))
207
+ assert_equal(false, fa.accepts?('? hello w o r'))
208
+ assert_equal(false, fa.accepts?('? hello w o r l'))
209
+ end
210
+
211
+ # Tests ADL::parse on the documentation succint example
212
+ def test_valid_adl_automaton_succint_example
213
+ fa = nil
214
+ assert_nothing_raised do
215
+ fa = ADL::parse_automaton <<-AUTOMATON
216
+ # Some header comments: tool which has generated this automaton,
217
+ # maybe a date or other tool options ...
218
+ # here: 'this automaton accepts the a(ba)* regular language'
219
+ 2 2
220
+ 0 true false
221
+ 1 false true
222
+ 0 1 a
223
+ 1 0 b
224
+ AUTOMATON
225
+ end
226
+ assert_equal(2, fa.state_count)
227
+ assert_equal(2, fa.edge_count)
228
+ assert_equal(true, fa.accepts?('? a'))
229
+ assert_equal(true, fa.accepts?('? a b a'))
230
+ assert_equal(true, fa.accepts?('? a b a b a'))
231
+ assert_equal(false, fa.accepts?('?'))
232
+ assert_equal(false, fa.accepts?('? a b'))
233
+ assert_equal(false, fa.accepts?('? a b a b'))
234
+ end
235
+
236
+ # Checks that an initial state may arruve lately
237
+ def test_parse_automaton_allows_late_initial_state
238
+ fa = nil
239
+ assert_nothing_raised do
240
+ fa = ADL::parse_automaton <<-AUTOMATON
241
+ # Some header comments: tool which has generated this automaton,
242
+ # maybe a date or other tool options ...
243
+ # here: 'this automaton accepts the a(ba)* regular language'
244
+ 2 2
245
+ 0 false false
246
+ 1 true true
247
+ 0 1 a
248
+ 1 0 b
249
+ AUTOMATON
250
+ end
251
+ end
252
+
253
+ # Tests parse_automaton on an automated randomly generated using jail
254
+ def test_parse_automaton_on_jail_randdfa
255
+ fa = nil
256
+ assert_nothing_raised do
257
+ fa = ADL::parse_automaton_file(File.join(File.dirname(__FILE__),'randdfa.adl'))
258
+ end
259
+ end
260
+
261
+ # Tests an important security issue about parse_automaton
262
+ def test_parse_automaton_does_not_executes_ruby_code
263
+ begin
264
+ assert_raise ADL::ParseError do
265
+ ADL::parse_automaton <<-AUTOMATON
266
+ Kernel.exit(-1)
267
+ AUTOMATON
268
+ end
269
+ rescue SystemExit
270
+ assert false, 'SECURITY issue: ADL::parse_automaton executes ruby code'
271
+ end
272
+ begin
273
+ assert_raise ADL::ParseError do
274
+ ADL::parse_automaton_file(File.dirname(__FILE__)+'/exit.rb')
275
+ end
276
+ rescue SystemExit
277
+ assert false, 'SECURITY issue: ADL::parse_automaton executes ruby code'
278
+ end
279
+ end
280
+
281
+ # Tests ADL::parse_string
282
+ def test_parse_string
283
+ s = ADL::parse_string('?')
284
+ assert_equal(true, InputString===s)
285
+ assert_equal(false, s.positive?)
286
+ assert_equal(false, s.negative?)
287
+ assert_equal(true, s.unlabeled?)
288
+ assert_equal(nil, s.label)
289
+ assert_equal(true, s.empty?)
290
+ assert_equal([], s.symbols)
291
+
292
+ s = ADL::parse_string('+')
293
+ assert_equal(true, InputString===s)
294
+ assert_equal(true, s.positive?)
295
+ assert_equal(true, s.label)
296
+ assert_equal(true, s.empty?)
297
+ assert_equal([], s.symbols)
298
+
299
+ s = ADL::parse_string('-')
300
+ assert_equal(true, InputString===s)
301
+ assert_equal(false, s.positive?)
302
+ assert_equal(false, s.label)
303
+ assert_equal(true, s.empty?)
304
+ assert_equal([], s.symbols)
305
+
306
+ s = ADL::parse_string('? a')
307
+ assert_equal(['a'], s.symbols)
308
+ assert_equal(false, s.positive?)
309
+ assert_equal(false, s.negative?)
310
+ assert_equal(true, s.unlabeled?)
311
+ assert_equal(nil, s.label)
312
+ assert_equal(['a'], s.symbols)
313
+
314
+ s = ADL::parse_string('+ a')
315
+ assert_equal(['a'], s.symbols)
316
+ assert_equal(true, s.positive?)
317
+
318
+ s = ADL::parse_string('- a')
319
+ assert_equal(['a'], s.symbols)
320
+ assert_equal(false, s.positive?)
321
+
322
+ s = ADL::parse_string('+ a b a b ')
323
+ assert_equal(['a','b','a','b'], s.symbols)
324
+ assert_equal(true, s.positive?)
325
+
326
+ s = ADL::parse_string('- a b a c')
327
+ assert_equal(['a','b','a','c'], s.symbols)
328
+ assert_equal(false, s.positive?)
329
+ end
330
+
331
+ # Tests ADL::parse_sample
332
+ def test_parse_sample
333
+ sample = ADL::parse_sample <<-SAMPLE
334
+ + a b a b a b
335
+ # this is a comment, next is an empty line
336
+ +
337
+ + a b
338
+ - a a
339
+ ? a b
340
+ # trailing comment allowed
341
+ SAMPLE
342
+ assert sample==Sample['+ a b a b a b', '+ a b', '- a a', '+', '? a b']
343
+ end
344
+
345
+ # Tests that ADL::parse_sample accepts the empty sample
346
+ def test_parse_sample_accepts_empty_sample
347
+ samples = [
348
+ ADL::parse_sample(""),
349
+ ADL::parse_sample("#"),
350
+ ADL::parse_sample(<<-SAMPLE
351
+ SAMPLE
352
+ ),
353
+ ADL::parse_sample(<<-SAMPLE
354
+
355
+ # this is a comment, between two empty lines
356
+
357
+ SAMPLE
358
+ )
359
+ ]
360
+ samples.each do |sample|
361
+ assert sample==Sample.new
362
+ end
363
+ end
364
+
365
+ # Tests that ADL::parse_sample accepts empty strings
366
+ def test_parse_sample_accepts_empty_strings
367
+ assert Sample['+'] == ADL::parse_sample('+')
368
+ assert Sample['-'] == ADL::parse_sample('-')
369
+ assert Sample['+'] == ADL::parse_sample(<<-SAMPLE
370
+ +
371
+ SAMPLE
372
+ )
373
+ assert Sample['-'] == ADL::parse_sample(<<-SAMPLE
374
+ -
375
+ SAMPLE
376
+ )
377
+ end
378
+
379
+ # Tests validity of sample.adl file
380
+ def test_valid_adl_sample_example
381
+ here = File.dirname(__FILE__)
382
+ sample_adl = File.join(here, '..', '..', 'example', 'adl', 'sample.adl')
383
+ sample = ADL::parse_sample_file(sample_adl)
384
+ expected = Sample.new
385
+ expected << InputString.new(['a', 'b', 'a', 'b'], true)
386
+ expected << InputString.new(['a', 'a'], false)
387
+ expected << InputString.new(['a', 'b'], nil)
388
+ expected << InputString.new([], true)
389
+ expected << InputString.new(['hello', 'world'], true)
390
+ expected << InputString.new(['h','e','l','l','o','w','o','r','l','d'], true)
391
+ expected << InputString.new(['helloworld'], true)
392
+ expected << InputString.new(['a','+','b','-','a','-','b','+a'], true)
393
+ expected << InputString.new(['#','a','#','b','a','b','#','and','all','these','words','are','symbols', 'too', '!!'],true)
394
+ expected.each do |s|
395
+ assert sample.include?(s), "|#{s}| from expected is included in sample"
396
+ end
397
+ sample.each do |s|
398
+ assert expected.include?(s), "|#{s}| from sample is included in expected"
399
+ end
400
+ assert expected == sample
401
+ end
402
+
403
+ # Tests validity of sample.adl file
404
+ def test_valid_adl_sample_succint_example
405
+ sample = ADL::parse_sample <<-SAMPLE
406
+ # Some header comments: tool which has generated this sample,
407
+ # maybe a date or other tool options ...
408
+ # here: 'this sample is caracteristic for the a(ba)* regular language'
409
+ -
410
+ + a
411
+ - a b
412
+ + a b a
413
+ SAMPLE
414
+ expected = Sample.new
415
+ expected << InputString.new([], false)
416
+ expected << InputString.new(['a'], true)
417
+ expected << InputString.new(['a','b'], false)
418
+ expected << InputString.new(['a','b','a'], true)
419
+ assert expected==sample
420
+ end
421
+
422
+ # Tests an important security issue about parse_automaton
423
+ def test_parse_sample_does_not_executes_ruby_code
424
+ begin
425
+ ADL::parse_sample <<-AUTOMATON
426
+ + Kernel.exit(-1)
427
+ AUTOMATON
428
+ rescue SystemExit
429
+ assert false, 'SECURITY issue: ADL::parse_automaton executes ruby code'
430
+ end
431
+ begin
432
+ ADL::parse_sample_file(File.dirname(__FILE__)+'/exit.rb')
433
+ rescue SystemExit
434
+ assert false, 'SECURITY issue: ADL::parse_automaton executes ruby code'
435
+ end
436
+ end
437
+
438
+ # tests that state IDs are loaded and can be used.
439
+ def test_state_names
440
+ fa = ADL::parse_automaton <<-AUTOMATON
441
+ 3 4
442
+ A true false
443
+ B false false
444
+ C false true
445
+ A B a
446
+ B C b
447
+ C C a
448
+ C B b
449
+ AUTOMATON
450
+
451
+ ['A','B','C'].each do |statename|
452
+ assert_equal statename,fa.get_state(statename)[:name]
453
+ end
454
+
455
+ assert_equal true,fa.get_state('A').initial?
456
+ assert_equal false,fa.get_state('B').initial?
457
+ assert_equal false,fa.get_state('C').initial?
458
+
459
+ assert_equal false,fa.get_state('A').accepting?
460
+ assert_equal false,fa.get_state('B').accepting?
461
+ assert_equal true,fa.get_state('C').accepting?
462
+ end
463
+
464
+ def test_parsing_recognizes_failures
465
+ assert_raise Stamina::ADL::ParseError do
466
+ fa = ADL::parse_sample <<-EOF
467
+ 3 4
468
+ A true false
469
+ B false false
470
+ C false true
471
+ A B a
472
+ B C b
473
+ C C a
474
+ C B b
475
+ EOF
476
+ end
477
+ assert_raise Stamina::ADL::ParseError do
478
+ sample = ADL::parse_automaton <<-EOF
479
+ + a b a b a b
480
+ # this is a comment, next is an empty line
481
+ +
482
+ + a b
483
+ - a a
484
+ a b
485
+ # trailing comment allowed
486
+ EOF
487
+ end
488
+ end
489
+ end # class ADLTest
490
+
491
+ end # module Stamina