rubyslim 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +16 -0
  4. data/README.md +187 -0
  5. data/Rakefile +21 -0
  6. data/bin/rubyslim +10 -0
  7. data/lib/rubyslim/list_deserializer.rb +67 -0
  8. data/lib/rubyslim/list_executor.rb +12 -0
  9. data/lib/rubyslim/list_serializer.rb +43 -0
  10. data/lib/rubyslim/ruby_slim.rb +61 -0
  11. data/lib/rubyslim/slim_error.rb +3 -0
  12. data/lib/rubyslim/slim_helper_library.rb +23 -0
  13. data/lib/rubyslim/socket_service.rb +53 -0
  14. data/lib/rubyslim/statement.rb +79 -0
  15. data/lib/rubyslim/statement_executor.rb +174 -0
  16. data/lib/rubyslim/table_to_hash_converter.rb +34 -0
  17. data/lib/test_module/library_new.rb +13 -0
  18. data/lib/test_module/library_old.rb +12 -0
  19. data/lib/test_module/should_not_find_test_slim_in_here/test_slim.rb +7 -0
  20. data/lib/test_module/simple_script.rb +9 -0
  21. data/lib/test_module/test_chain.rb +5 -0
  22. data/lib/test_module/test_slim.rb +86 -0
  23. data/lib/test_module/test_slim_with_arguments.rb +23 -0
  24. data/lib/test_module/test_slim_with_no_sut.rb +5 -0
  25. data/rubyslim.gemspec +21 -0
  26. data/spec/instance_creation_spec.rb +40 -0
  27. data/spec/it8f_spec.rb +9 -0
  28. data/spec/list_deserializer_spec.rb +67 -0
  29. data/spec/list_executor_spec.rb +187 -0
  30. data/spec/list_serialzer_spec.rb +39 -0
  31. data/spec/method_invocation_spec.rb +128 -0
  32. data/spec/slim_helper_library_spec.rb +80 -0
  33. data/spec/socket_service_spec.rb +98 -0
  34. data/spec/spec_helper.rb +2 -0
  35. data/spec/statement_executor_spec.rb +50 -0
  36. data/spec/statement_spec.rb +17 -0
  37. data/spec/table_to_hash_converter_spec.rb +32 -0
  38. metadata +100 -0
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .rakeTasks
2
+ *.iml
3
+ *.ipr
4
+ *.iws
5
+ .DS_Store
6
+ .idea
7
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rubyslim (0.1.1)
5
+
6
+ GEM
7
+ remote: http://www.rubygems.org/
8
+ specs:
9
+ rspec (1.3.2)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ rspec (~> 1.3.0)
16
+ rubyslim!
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ Ruby Slim
2
+ =========
3
+
4
+ This package provides a SliM server implementing the [FitNesse](http://fitnesse.org)
5
+ [SliM protocol](http://fitnesse.org/FitNesse.UserGuide.SliM.SlimProtocol). It allows
6
+ you to write test fixtures in Ruby, and invoke them from a FitNesse test.
7
+
8
+
9
+ Fixture names
10
+ -------------
11
+
12
+ Rubyslim is very particular about how your modules and classes are named, and
13
+ how you import and use them in your FitNesse wiki:
14
+
15
+ * Fixture folder must be `lowercase_and_underscore`
16
+ * Fixture filenames must be `lowercase_and_underscore`
17
+ * Ruby module name must be the `CamelCase` version of fixture folder name
18
+ * Ruby class name must be the `CamelCase` version of the fixture file name
19
+
20
+ For example, this naming scheme is valid:
21
+
22
+ * Folder: `ruby_fix`
23
+ * Filename: `my_fixture.rb`
24
+ * Module: `RubyFix`
25
+ * Class: `MyFixture`
26
+
27
+ If you have `TwoWords` in CamelCase, then that would be `two_words` with
28
+ underscores. If you have only `oneword` in the lowercase version, then you must
29
+ have `Oneword` in the CamelCase version. If all of these naming conventions are
30
+ not exactly followed, you'll get mysterious errors like `Could not invoke
31
+ constructor` for your Slim tables.
32
+
33
+
34
+ Setup
35
+ -----
36
+
37
+ Put these commands in a parent of the Ruby test pages.
38
+
39
+ !define TEST_SYSTEM {slim}
40
+ !define TEST_RUNNER {rubyslim}
41
+ !define COMMAND_PATTERN {rubyslim}
42
+ !path your/ruby/fixtures
43
+
44
+ Paths can be relative. You should put the following in an appropriate SetUp page:
45
+
46
+ !|import|
47
+ |<ruby module of fixtures>|
48
+
49
+ You can have as many rows in this table as you like, one for each module that
50
+ contains fixtures. Note that this needs to be the *name* of the module as
51
+ written in the Ruby code, not the filename where the module is defined.
52
+
53
+ Ruby slim works a lot like Java slim. We tried to use ruby method naming
54
+ conventions. So if you put this in a table:
55
+
56
+ |SomeDecisionTable|
57
+ |input|get output?|
58
+ |1 |2 |
59
+
60
+ Then it will call the `set_input` and `get_output` functions of the
61
+ `SomeDecisionTable` class.
62
+
63
+ The `SomeDecisionTable` class would be written in a file called
64
+ `some_decision_table.rb`, like this (the file name must correspond to the class
65
+ name defined within, and the module name must match the one you are importing):
66
+
67
+ module MyModule
68
+ class SomeDecisionTable
69
+ def set_input(input)
70
+ @x = input
71
+ end
72
+
73
+ def get_output
74
+ @x
75
+ end
76
+ end
77
+ end
78
+
79
+ Note that there is no type information for the arguments of these functions, so
80
+ they will all be treated as strings. This is important to remember! All
81
+ arguments are strings. If you are expecting integers, you will have to convert
82
+ the strings to integers within your fixtures.
83
+
84
+
85
+ Hashes
86
+ ------
87
+
88
+ There is one exception to the above rule. If you pass a HashWidget in a table,
89
+ then the argument will be converted to a Hash.
90
+
91
+ Consider, for example, this fixtures class:
92
+
93
+ module TestModule
94
+ class TestSlimWithArguments
95
+ def initialize(arg)
96
+ @arg = arg
97
+ end
98
+
99
+ def arg
100
+ @arg
101
+ end
102
+
103
+ def name
104
+ @arg[:name]
105
+ end
106
+
107
+ def addr
108
+ @arg[:addr]
109
+ end
110
+
111
+ def set_arg(hash)
112
+ @arg = hash
113
+ end
114
+ end
115
+ end
116
+
117
+ This corresponds to the following tables.
118
+
119
+ |script|test slim with arguments|!{name:bob addr:here}|
120
+ |check|name|bob|
121
+ |check|addr|here|
122
+
123
+ |script|test slim with arguments|gunk|
124
+ |check|arg|gunk|
125
+ |set arg|!{name:bob addr:here}|
126
+ |check|name|bob|
127
+ |check|addr|here|
128
+
129
+ Note the use of the HashWidgets in the table cells. These get translated into
130
+ HTML, which RubySlim recognizes and converts to a standard ruby `Hash`.
131
+
132
+
133
+ System Under Test
134
+ -----------------
135
+
136
+ If a fixture has a `sut` method that returns an object, then if a method called
137
+ by a test is not found in the fixture itself, then if it exists in the object
138
+ returned by `sut` it will be called. For example:
139
+
140
+ !|script|my fixture|
141
+ |func|1|
142
+
143
+ class MySystem
144
+ def func(x)
145
+ #this is the function that will be called.
146
+ end
147
+ end
148
+
149
+ class MyFixture
150
+ attr_reader :sut
151
+
152
+ def initialize
153
+ @sut = MySystem.new
154
+ end
155
+ end
156
+
157
+ Since the fixture `MyFixture` does not have a function named `func`, but it
158
+ _does_ have a method named `sut`, RubySlim will try to call `func` on the
159
+ object returned by `sut`.
160
+
161
+
162
+ Library Fixtures
163
+ ----------------
164
+
165
+ Ruby Slim supports the `|Library|` feature of FitNesse. If you declare certain
166
+ classes to be libraries, then if a test calls a method, and the specified
167
+ fixture does not have it, and there is no specified `sut`, then the libraries
168
+ will be searched in reverse order (latest first). If the method is found, then
169
+ it is called.
170
+
171
+ For example:
172
+
173
+ |Library|
174
+ |echo fixture|
175
+
176
+ |script|
177
+ |check|echo|a|a|
178
+
179
+ class EchoFixture
180
+ def echo(x)
181
+ x
182
+ end
183
+ end
184
+
185
+ Here, even though no fixture was specified for the script table, since a
186
+ library was declared, functions will be called on it.
187
+
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake'
2
+ #require 'rcov/rcovtask'
3
+ require 'spec/rake/spectask'
4
+
5
+ task :default => :spec
6
+
7
+ desc "Run all spec tests"
8
+ Spec::Rake::SpecTask.new(:spec) do |t|
9
+ t.spec_files = Dir.glob('spec/**/*_spec.rb')
10
+ t.spec_opts = ['--color', '--format specdoc']
11
+ end
12
+
13
+ desc "Run all spec tests and generate coverage report"
14
+ Spec::Rake::SpecTask.new(:rcov) do |t|
15
+ t.spec_files = Dir.glob('spec/**/*_spec.rb')
16
+ # RCov doesn't like this part for some reason
17
+ #t.spec_opts = ['--color', '--format specdoc']
18
+ t.rcov = true
19
+ t.rcov_opts = %w{--exclude osx\/objc,gems\/,spec\/,features\/,lib\/test_module\/}
20
+ end
21
+
data/bin/rubyslim ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib/"))
4
+
5
+ require 'rubyslim/ruby_slim'
6
+
7
+ port = ARGV[0].to_i
8
+ rubySlim = RubySlim.new
9
+ rubySlim.run(port)
10
+
@@ -0,0 +1,67 @@
1
+ module ListDeserializer
2
+ class SyntaxError < Exception
3
+ end
4
+
5
+ # De-serialize the given string, and return a Ruby-native list.
6
+ # Raises a SyntaxError if the string is empty or badly-formatted.
7
+ def self.deserialize(string)
8
+ raise SyntaxError.new("Can't deserialize null") if string.nil?
9
+ raise SyntaxError.new("Can't deserialize empty string") if string.empty?
10
+ raise SyntaxError.new("Serialized list has no starting [") if string[0..0] != "["
11
+ raise SyntaxError.new("Serialized list has no ending ]") if string[-1..-1] != "]"
12
+ Deserializer.new(string).deserialize
13
+ end
14
+
15
+
16
+ class Deserializer
17
+ def initialize(string)
18
+ @string = string;
19
+ end
20
+
21
+ def deserialize
22
+ @pos = 1
23
+ @list = []
24
+ number_of_items = consume_length
25
+
26
+ # For each item in the list
27
+ number_of_items.times do
28
+ length_of_item = consume_length
29
+ item = @string[@pos...@pos+length_of_item]
30
+ length_in_bytes = length_of_item
31
+
32
+ until (item.length > length_of_item) do
33
+ length_in_bytes += 1
34
+ item = @string[@pos...@pos+length_in_bytes]
35
+ end
36
+
37
+ length_in_bytes -= 1
38
+ item = @string[@pos...@pos+length_in_bytes]
39
+
40
+ # Ensure the ':' list-termination character is found
41
+ term_char = @string[@pos+length_in_bytes,1]
42
+ if term_char != ':'
43
+ raise SyntaxError.new("List termination character ':' not found" +
44
+ " (got '#{term_char}' instead)")
45
+ end
46
+
47
+ @pos += length_in_bytes+1
48
+ begin
49
+ sublist = ListDeserializer.deserialize(item)
50
+ @list << sublist
51
+ rescue ListDeserializer::SyntaxError
52
+ @list << item
53
+ end
54
+ end
55
+ @list
56
+ end
57
+
58
+ # Consume the 6-digit length prefix, and return the
59
+ # length as an integer.
60
+ def consume_length
61
+ length = @string[@pos...@pos+6].to_i
62
+ @pos += 7
63
+ length
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,12 @@
1
+ require "rubyslim/statement"
2
+ require "rubyslim/statement_executor"
3
+
4
+ class ListExecutor
5
+ def initialize()
6
+ @executor = StatementExecutor.new
7
+ end
8
+
9
+ def execute(instructions)
10
+ instructions.collect {|instruction| Statement.execute(instruction, @executor)}
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ module ListSerializer
2
+ # Serialize a list according to the SliM protocol.
3
+ #
4
+ # Lists are enclosed in square-brackets '[...]'. Inside the opening
5
+ # bracket is a six-digit number indicating the length of the list
6
+ # (number of items), then a colon ':', then the serialization of each
7
+ # list item. For example:
8
+ #
9
+ # [] => "[000000:]"
10
+ # ["hello"] => "[000001:000005:hello:]"
11
+ # [1] => "[000001:000001:1:]"
12
+ #
13
+ # Strings are preceded by a six-digit sequence indicating their length:
14
+ #
15
+ # "" => "000000:"
16
+ # "hello" => "000005:hello"
17
+ # nil => "000004:null"
18
+ #
19
+ # See spec/list_serializer_spec.rb for more examples.
20
+ #
21
+ def self.serialize(list)
22
+ result = "["
23
+ result += length_string(list.length)
24
+
25
+ # Serialize each item in the list
26
+ list.each do |item|
27
+ item = "null" if item.nil?
28
+ item = serialize(item) if item.is_a?(Array)
29
+ item = item.to_s
30
+ result += length_string(item.length)
31
+ result += item + ":"
32
+ end
33
+
34
+ result += "]"
35
+ end
36
+
37
+
38
+ # Return the six-digit prefix for an element of the given length.
39
+ def self.length_string(length)
40
+ sprintf("%06d:",length)
41
+ end
42
+ end
43
+
@@ -0,0 +1,61 @@
1
+ require "rubyslim/socket_service"
2
+ require "rubyslim/list_deserializer"
3
+ require "rubyslim/list_serializer"
4
+ require "rubyslim/list_executor"
5
+
6
+ class RubySlim
7
+ def run(port)
8
+ @connected = true
9
+ @executor = ListExecutor.new
10
+ socket_service = SocketService.new()
11
+ socket_service.serve(port) do |socket|
12
+ serve_ruby_slim(socket)
13
+ end
14
+
15
+ while (@connected)
16
+ sleep(0.1)
17
+ end
18
+ end
19
+
20
+ # Read and execute instructions from the SliM socket, until a 'bye'
21
+ # instruction is reached. Each instruction is a list, serialized as a string,
22
+ # following the SliM protocol:
23
+ #
24
+ # length:command
25
+ #
26
+ # Where `length` is a 6-digit indicating the length in bytes of `command`,
27
+ # and `command` is a serialized list of instructions that may include any
28
+ # of the four standard instructions in the SliM protocol:
29
+ #
30
+ # Import: [<id>, import, <path>]
31
+ # Make: [<id>, make, <instance>, <class>, <arg>...]
32
+ # Call: [<id>, call, <instance>, <function>, <arg>...]
33
+ # CallAndAssign: [<id>, callAndAssign, <symbol>, <instance>, <function>, <arg>...]
34
+ #
35
+ # (from http://fitnesse.org/FitNesse.UserGuide.SliM.SlimProtocol)
36
+ #
37
+ def serve_ruby_slim(socket)
38
+ socket.puts("Slim -- V0.3");
39
+ said_bye = false
40
+
41
+ while !said_bye
42
+ length = socket.read(6).to_i # <length>
43
+ socket.read(1) # :
44
+ command = socket.read(length) # <command>
45
+
46
+ # Until a 'bye' command is received, deserialize the command, execute the
47
+ # instructions, and write a serialized response back to the socket.
48
+ if command.downcase != "bye"
49
+ instructions = ListDeserializer.deserialize(command);
50
+ results = @executor.execute(instructions)
51
+ response = ListSerializer.serialize(results);
52
+ socket.write(sprintf("%06d:%s", response.length, response))
53
+ socket.flush
54
+ else
55
+ said_bye = true
56
+ end
57
+ end
58
+ @connected = false
59
+ end
60
+ end
61
+
@@ -0,0 +1,3 @@
1
+ class SlimError < Exception
2
+
3
+ end