rubyslim 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +16 -0
- data/README.md +187 -0
- data/Rakefile +21 -0
- data/bin/rubyslim +10 -0
- data/lib/rubyslim/list_deserializer.rb +67 -0
- data/lib/rubyslim/list_executor.rb +12 -0
- data/lib/rubyslim/list_serializer.rb +43 -0
- data/lib/rubyslim/ruby_slim.rb +61 -0
- data/lib/rubyslim/slim_error.rb +3 -0
- data/lib/rubyslim/slim_helper_library.rb +23 -0
- data/lib/rubyslim/socket_service.rb +53 -0
- data/lib/rubyslim/statement.rb +79 -0
- data/lib/rubyslim/statement_executor.rb +174 -0
- data/lib/rubyslim/table_to_hash_converter.rb +34 -0
- data/lib/test_module/library_new.rb +13 -0
- data/lib/test_module/library_old.rb +12 -0
- data/lib/test_module/should_not_find_test_slim_in_here/test_slim.rb +7 -0
- data/lib/test_module/simple_script.rb +9 -0
- data/lib/test_module/test_chain.rb +5 -0
- data/lib/test_module/test_slim.rb +86 -0
- data/lib/test_module/test_slim_with_arguments.rb +23 -0
- data/lib/test_module/test_slim_with_no_sut.rb +5 -0
- data/rubyslim.gemspec +21 -0
- data/spec/instance_creation_spec.rb +40 -0
- data/spec/it8f_spec.rb +9 -0
- data/spec/list_deserializer_spec.rb +67 -0
- data/spec/list_executor_spec.rb +187 -0
- data/spec/list_serialzer_spec.rb +39 -0
- data/spec/method_invocation_spec.rb +128 -0
- data/spec/slim_helper_library_spec.rb +80 -0
- data/spec/socket_service_spec.rb +98 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/statement_executor_spec.rb +50 -0
- data/spec/statement_spec.rb +17 -0
- data/spec/table_to_hash_converter_spec.rb +32 -0
- metadata +100 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
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,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
|
+
|