├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── bin └── rubyslim ├── lib ├── rubyslim │ ├── list_deserializer.rb │ ├── list_executor.rb │ ├── list_serializer.rb │ ├── ruby_slim.rb │ ├── slim_error.rb │ ├── slim_helper_library.rb │ ├── socket_service.rb │ ├── statement.rb │ ├── statement_executor.rb │ └── table_to_hash_converter.rb └── test_module │ ├── library_new.rb │ ├── library_old.rb │ ├── should_not_find_test_slim_in_here │ └── test_slim.rb │ ├── simple_script.rb │ ├── test_chain.rb │ ├── test_slim.rb │ ├── test_slim_with_arguments.rb │ └── test_slim_with_no_sut.rb ├── rubyslim.gemspec └── spec ├── instance_creation_spec.rb ├── it8f_spec.rb ├── list_deserializer_spec.rb ├── list_executor_spec.rb ├── list_serialzer_spec.rb ├── method_invocation_spec.rb ├── slim_helper_library_spec.rb ├── socket_service_spec.rb ├── spec_helper.rb ├── statement_executor_spec.rb ├── statement_spec.rb └── table_to_hash_converter_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .rakeTasks 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .DS_Store 6 | .idea 7 | *.gem 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://www.rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 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! 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | || 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 | 188 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 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 | 22 | -------------------------------------------------------------------------------- /bin/rubyslim: -------------------------------------------------------------------------------- 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 | 11 | -------------------------------------------------------------------------------- /lib/rubyslim/list_deserializer.rb: -------------------------------------------------------------------------------- 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 68 | -------------------------------------------------------------------------------- /lib/rubyslim/list_executor.rb: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /lib/rubyslim/list_serializer.rb: -------------------------------------------------------------------------------- 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 | 44 | -------------------------------------------------------------------------------- /lib/rubyslim/ruby_slim.rb: -------------------------------------------------------------------------------- 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: [, import, ] 31 | # Make: [, make, , , ...] 32 | # Call: [, call, , , ...] 33 | # CallAndAssign: [, callAndAssign, , , , ...] 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 # 43 | socket.read(1) # : 44 | command = socket.read(length) # 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 | 62 | -------------------------------------------------------------------------------- /lib/rubyslim/slim_error.rb: -------------------------------------------------------------------------------- 1 | class SlimError < Exception 2 | 3 | end -------------------------------------------------------------------------------- /lib/rubyslim/slim_helper_library.rb: -------------------------------------------------------------------------------- 1 | class SlimHelperLibrary 2 | ACTOR_INSTANCE_NAME = "scriptTableActor" 3 | attr_accessor :executor 4 | 5 | def initialize(executor = nil) 6 | @executor = executor 7 | @fixtures = [] 8 | end 9 | 10 | def get_fixture 11 | executor.instance(ACTOR_INSTANCE_NAME) 12 | end 13 | 14 | def push_fixture 15 | @fixtures << get_fixture 16 | nil 17 | end 18 | 19 | def pop_fixture 20 | executor.set_instance(ACTOR_INSTANCE_NAME, @fixtures.pop) 21 | nil 22 | end 23 | end -------------------------------------------------------------------------------- /lib/rubyslim/socket_service.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'thread' 3 | 4 | 5 | class SocketService 6 | 7 | attr_reader :closed 8 | 9 | def initialize() 10 | @ropeSocket = nil 11 | @group = ThreadGroup.new 12 | @serviceThread = nil 13 | end 14 | 15 | def serve(port, &action) 16 | @closed = false 17 | @action = action 18 | @ropeSocket = TCPServer.open(port) 19 | @serviceThread = Thread.start {serviceTask} 20 | @group.add(@serviceThread) 21 | end 22 | 23 | def pendingSessions 24 | @group.list.size - ((@serviceThread != nil) ? 1 : 0) 25 | end 26 | 27 | def serviceTask 28 | while true 29 | Thread.start(@ropeSocket.accept) do |s| 30 | serverTask(s) 31 | end 32 | end 33 | end 34 | 35 | def serverTask(s) 36 | @action.call(s) 37 | s.close 38 | end 39 | 40 | def close 41 | @serviceThread.kill 42 | @serviceThread = nil 43 | @ropeSocket.close 44 | waitForServers 45 | @closed = true 46 | end 47 | 48 | def waitForServers 49 | @group.list.each do |t| 50 | t.join 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rubyslim/statement.rb: -------------------------------------------------------------------------------- 1 | require "rubyslim/statement_executor" 2 | 3 | class Statement 4 | EXCEPTION_TAG = "__EXCEPTION__:" 5 | def self.execute(statement, executor) 6 | Statement.new(statement).exec(executor) 7 | end 8 | 9 | def initialize(statement) 10 | @statement = statement 11 | end 12 | 13 | def exec(executor) 14 | @executor = executor 15 | begin 16 | case(operation) 17 | when "make" 18 | instance_name = get_word(2) 19 | class_name = slim_to_ruby_class(get_word(3)) 20 | [id, @executor.create(instance_name, class_name, get_args(4))] 21 | when "import" 22 | @executor.add_module(slim_to_ruby_class(get_word(2))) 23 | [id, "OK"] 24 | when "call" 25 | call_method_at_index(2) 26 | when "callAndAssign" 27 | result = call_method_at_index(3) 28 | @executor.set_symbol(get_word(2), result[1]) 29 | result 30 | else 31 | [id, EXCEPTION_TAG + "message:<>"] 32 | end 33 | rescue SlimError => e 34 | [id, EXCEPTION_TAG + e.message] 35 | rescue Exception => e 36 | [id, EXCEPTION_TAG + e.message + "\n" + e.backtrace.join("\n")] 37 | end 38 | 39 | end 40 | 41 | def call_method_at_index(index) 42 | instance_name = get_word(index) 43 | method_name = slim_to_ruby_method(get_word(index+1)) 44 | args = get_args(index+2) 45 | [id, @executor.call(instance_name, method_name, *args)] 46 | end 47 | 48 | def slim_to_ruby_class(class_name) 49 | parts = class_name.split(/\.|\:\:/) 50 | converted = parts.collect {|part| part[0..0].upcase+part[1..-1]} 51 | converted.join("::") 52 | end 53 | 54 | def slim_to_ruby_method(method_name) 55 | value = method_name[0..0].downcase + method_name[1..-1] 56 | value.gsub(/[A-Z]/) { |cap| "_#{cap.downcase}" } 57 | end 58 | 59 | def id 60 | get_word(0) 61 | end 62 | 63 | def operation 64 | get_word(1) 65 | end 66 | 67 | def get_word(index) 68 | check_index(index) 69 | @statement[index] 70 | end 71 | 72 | def get_args(index) 73 | @statement[index..-1] 74 | end 75 | 76 | def check_index(index) 77 | raise SlimError.new("message:<>") if index >= @statement.length 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/rubyslim/statement_executor.rb: -------------------------------------------------------------------------------- 1 | require "rubyslim/slim_error" 2 | require "rubyslim/statement" 3 | require "rubyslim/table_to_hash_converter" 4 | require "rubyslim/slim_helper_library" 5 | 6 | class StatementExecutor 7 | def initialize 8 | @instances = {} 9 | @modules = [] 10 | @symbols = {} 11 | @libraries = [SlimHelperLibrary.new(self)] 12 | end 13 | 14 | def library?(instance_name) 15 | library_prefix = "library" 16 | instance_name[0, library_prefix.length] == library_prefix 17 | end 18 | 19 | def create(instance_name, class_name, constructor_arguments) 20 | begin 21 | instance = replace_symbol(class_name) 22 | if instance.is_a?(String) 23 | instance = construct_instance(instance, replace_symbols(constructor_arguments)) 24 | if library?(instance_name) 25 | @libraries << instance 26 | end 27 | end 28 | set_instance(instance_name, instance) 29 | "OK" 30 | rescue SlimError => e 31 | Statement::EXCEPTION_TAG + e.to_s 32 | end 33 | end 34 | 35 | def set_instance(instance_name, instance) 36 | @instances[instance_name] = instance 37 | end 38 | 39 | def construct_instance(class_name, constructor_arguments) 40 | require_class(class_name); 41 | construct(class_name, constructor_arguments); 42 | end 43 | 44 | 45 | def make_path_to_class(class_name) 46 | module_names = split_class_name(class_name) 47 | files = module_names.collect { |module_name| to_file_name(module_name) } 48 | File.join(files) 49 | end 50 | 51 | def split_class_name(class_name) 52 | class_name.split(/\:\:/) 53 | end 54 | 55 | def replace_tables_with_hashes(constructor_arguments) 56 | args = constructor_arguments.map do |arg| 57 | TableToHashConverter.convert arg 58 | end 59 | return args 60 | end 61 | 62 | def construct(class_name, constructor_arguments) 63 | class_object = get_class(class_name) 64 | begin 65 | class_object.new(*replace_tables_with_hashes(constructor_arguments)) 66 | rescue ArgumentError => e 67 | raise SlimError.new("message:<>") 68 | end 69 | end 70 | 71 | 72 | def with_each_fully_qualified_class_name(class_name, &block) 73 | (@modules.map { |module_name| module_name + "::" + class_name } << class_name).reverse.each &block 74 | end 75 | 76 | def require_class(class_name) 77 | with_each_fully_qualified_class_name(class_name) do |fully_qualified_name| 78 | begin 79 | require make_path_to_class(fully_qualified_name) 80 | return 81 | rescue LoadError 82 | end 83 | end 84 | raise SlimError.new("message:<>") 85 | end 86 | 87 | def get_class(class_name) 88 | with_each_fully_qualified_class_name(class_name) do |fully_qualified_name| 89 | begin 90 | return eval(fully_qualified_name) 91 | rescue NameError 92 | end 93 | end 94 | raise SlimError.new("message:<>") 95 | end 96 | 97 | def instance(instance_name) 98 | @instances[instance_name] 99 | end 100 | 101 | def to_file_name(module_name) 102 | value = module_name[0..0].downcase + module_name[1..-1] 103 | value.gsub(/[A-Z]/) { |cap| "_#{cap.downcase}" } 104 | end 105 | 106 | def send_message_to_instance(instance, method, args) 107 | symbols = replace_symbols(args) 108 | instance.send(method, *replace_tables_with_hashes(symbols)) 109 | end 110 | 111 | def method_to_call(instance, method_name) 112 | return nil unless instance 113 | return method_name.to_sym if instance.respond_to?(method_name) 114 | return "#{$1}=".to_sym if method_name =~ /set_(\w+)/ && instance.respond_to?("#{$1}=") 115 | return nil 116 | end 117 | 118 | def call(instance_name, method_name, *args) 119 | begin 120 | instance = self.instance(instance_name) 121 | if method = method_to_call(instance, method_name) 122 | send_message_to_instance(instance, method, args) 123 | elsif instance.respond_to?(:sut) && method = method_to_call(instance.sut, method_name) 124 | send_message_to_instance(instance.sut, method, args) 125 | else 126 | @libraries.reverse_each do |library| 127 | method = method_to_call(library, method_name) 128 | return send_message_to_instance(library, method, args) if method 129 | end 130 | raise SlimError.new("message:<>") if instance.nil? 131 | raise SlimError.new("message:<>") 132 | end 133 | rescue SlimError => e 134 | Statement::EXCEPTION_TAG + e.to_s 135 | end 136 | end 137 | 138 | def add_module(module_name) 139 | @modules << module_name.gsub(/\./, '::') 140 | end 141 | 142 | def set_symbol(name, value) 143 | @symbols[name] = value 144 | end 145 | 146 | def get_symbol(name) 147 | @symbols[name] 148 | end 149 | 150 | def acquire_symbol(symbol_text) 151 | symbol = get_symbol(symbol_text[1..-1]) 152 | symbol = symbol_text if symbol.nil? 153 | symbol 154 | end 155 | 156 | def replace_symbol(item) 157 | match = item.match(/\A\$\w*\z/) 158 | return acquire_symbol(match[0]) if match 159 | 160 | item.gsub(/\$\w*/) do |match| 161 | acquire_symbol(match) 162 | end 163 | end 164 | 165 | def replace_symbols(list) 166 | list.map do |item| 167 | if item.kind_of?(Array) 168 | replace_symbols(item) 169 | else 170 | replace_symbol(item) 171 | end 172 | end 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /lib/rubyslim/table_to_hash_converter.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | class TableToHashConverter 4 | def self.convert(string) 5 | begin 6 | doc = REXML::Document.new(string.strip) 7 | return html_to_hash(doc) 8 | rescue 9 | return string 10 | end 11 | end 12 | 13 | def self.html_to_hash(doc) 14 | table = doc.elements['table'] 15 | raise ArgumentError if table.nil? 16 | create_hash_from_rows(table) 17 | end 18 | 19 | def self.create_hash_from_rows(rows) 20 | hash = {} 21 | rows.elements.each('tr') do |row| 22 | add_row_to_hash(hash, row) 23 | end 24 | hash 25 | end 26 | 27 | def self.add_row_to_hash(hash, row) 28 | columns = row.get_elements('td') 29 | raise ArgumentError if columns.size != 2 30 | # Handle empty values. 31 | columns[1].text = '' if columns[1].text == nil 32 | hash[columns[0].text.strip.to_sym] = columns[1].text.strip 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/test_module/library_new.rb: -------------------------------------------------------------------------------- 1 | module TestModule 2 | class LibraryNew 3 | attr_reader :called 4 | attr_accessor :lib_attribute 5 | def method_on_library_new 6 | @called = true; 7 | end 8 | 9 | def a_method 10 | @called = true; 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /lib/test_module/library_old.rb: -------------------------------------------------------------------------------- 1 | module TestModule 2 | class LibraryOld 3 | attr_reader :called 4 | def method_on_library_old 5 | @called = true; 6 | end 7 | 8 | def a_method 9 | @called = true 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /lib/test_module/should_not_find_test_slim_in_here/test_slim.rb: -------------------------------------------------------------------------------- 1 | module TestModule::ShouldNotFindTestSlimInHere 2 | class TestSlim 3 | def return_string 4 | "blah" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/test_module/simple_script.rb: -------------------------------------------------------------------------------- 1 | class SimpleScript 2 | def set_arg(arg) 3 | @arg = arg 4 | end 5 | 6 | def get_arg 7 | @arg 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/test_module/test_chain.rb: -------------------------------------------------------------------------------- 1 | class TestChain 2 | def echo_boolean(b) 3 | b 4 | end 5 | end -------------------------------------------------------------------------------- /lib/test_module/test_slim.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module TestModule 4 | class SystemUnderTest 5 | attr_accessor :attribute 6 | def sut_method 7 | true 8 | end 9 | end 10 | 11 | class TestSlim 12 | attr_reader :sut 13 | attr_accessor :string 14 | 15 | def initialize(generation = 0) 16 | @generation = generation 17 | @sut = SystemUnderTest.new 18 | @string = "string" 19 | end 20 | 21 | def return_string 22 | @string 23 | end 24 | 25 | def returnString #Should not ever be called. 26 | "blah" 27 | end 28 | 29 | def add(a, b) 30 | a+b 31 | end 32 | 33 | def echo(x) 34 | x 35 | end 36 | 37 | def null 38 | nil 39 | end 40 | 41 | def echo_int i 42 | i 43 | end 44 | 45 | def echo_string s 46 | s 47 | end 48 | 49 | def syntax_error 50 | eval "1,2" 51 | end 52 | 53 | def utf8 54 | "Espa\357\277\275ol" 55 | end 56 | 57 | def create_test_slim_with_string(string) 58 | slim = TestSlim.new(@generation + 1) 59 | slim.string = string 60 | slim 61 | end 62 | 63 | def new_with_string(string) 64 | s = TestSlim.new 65 | s.string = string 66 | s 67 | end 68 | 69 | def echo_object(method, string) 70 | OpenStruct.new(method.to_sym => string) 71 | end 72 | 73 | def call_on(method, object) 74 | object.send(method.to_sym) 75 | end 76 | 77 | # def is_same(other) 78 | # self === other 79 | # end 80 | # 81 | # def get_string_from_other other 82 | # other.get_string_arg 83 | # end 84 | 85 | end 86 | end -------------------------------------------------------------------------------- /lib/test_module/test_slim_with_arguments.rb: -------------------------------------------------------------------------------- 1 | module TestModule 2 | class TestSlimWithArguments 3 | def initialize(arg) 4 | @arg = arg 5 | end 6 | 7 | def arg 8 | @arg 9 | end 10 | 11 | def name 12 | @arg[:name] 13 | end 14 | 15 | def addr 16 | @arg[:addr] 17 | end 18 | 19 | def set_arg(hash) 20 | @arg = hash 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /lib/test_module/test_slim_with_no_sut.rb: -------------------------------------------------------------------------------- 1 | module TestModule 2 | class TestSlimWithNoSut 3 | 4 | end 5 | end -------------------------------------------------------------------------------- /rubyslim.gemspec: -------------------------------------------------------------------------------- 1 | 2 | Gem::Specification.new do |s| 3 | s.name = "rubyslim" 4 | s.version = "0.1.1" 5 | s.summary = "Ruby SliM protocol for FitNesse" 6 | s.description = <<-EOS 7 | RubySliM implements the SliM protocol for the FitNesse 8 | acceptance testing framework. 9 | EOS 10 | s.authors = ["Robert C. Martin", "Doug Bradbury"] 11 | s.email = "unclebob@cleancoder.com" 12 | s.platform = Gem::Platform::RUBY 13 | 14 | s.add_development_dependency 'rspec', '~> 1.3.0' 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.require_path = 'lib' 18 | 19 | s.executables = ['rubyslim'] 20 | end 21 | 22 | -------------------------------------------------------------------------------- /spec/instance_creation_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require "statement_executor" 3 | 4 | describe StatementExecutor do 5 | before do 6 | @caller = StatementExecutor.new 7 | end 8 | 9 | it "can create an instance" do 10 | response = @caller.create("x", "TestModule::TestSlim", []) 11 | response.should == "OK" 12 | x = @caller.instance("x") 13 | x.class.name.should == "TestModule::TestSlim" 14 | end 15 | 16 | it "can create an instance with arguments" do 17 | response = @caller.create("x", "TestModule::TestSlimWithArguments", ["3"]) 18 | response.should == "OK" 19 | x = @caller.instance("x") 20 | x.arg.should == "3" 21 | end 22 | 23 | it "can create an instance with arguments that are symbols" do 24 | @caller.set_symbol("X", "3") 25 | response = @caller.create("x", "TestModule::TestSlimWithArguments", ["$X"]) 26 | response.should == "OK" 27 | x = @caller.instance("x") 28 | x.arg.should == "3" 29 | end 30 | 31 | it "can't create an instance with the wrong number of arguments" do 32 | result = @caller.create("x", "TestModule::TestSlim", ["1", "noSuchArgument"]) 33 | result.should include(Statement::EXCEPTION_TAG + "message:<>") 34 | end 35 | 36 | it "can't create an instance if there is no class" do 37 | result = @caller.create("x", "TestModule::NoSuchClass", []) 38 | result.should include(Statement::EXCEPTION_TAG + "message:<>") 39 | end 40 | end -------------------------------------------------------------------------------- /spec/it8f_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 3 | 4 | describe "characters" do 5 | it "should be able to deal with it8f chars" do 6 | "Köln".should == "Köln" 7 | "Köln".size.should == 4 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/list_deserializer_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 3 | require "list_serializer" 4 | require "list_deserializer" 5 | 6 | describe ListDeserializer do 7 | before do 8 | @list = [] 9 | end 10 | 11 | it "can't deserialize a null string" do 12 | proc {ListDeserializer.deserialize(nil)}.should raise_error(ListDeserializer::SyntaxError) 13 | end 14 | 15 | it "can't deserialize empty string" do 16 | proc {ListDeserializer.deserialize("")}.should raise_error(ListDeserializer::SyntaxError) 17 | end 18 | 19 | it "can't deserialize string that doesn't start with an open bracket" do 20 | proc {ListDeserializer.deserialize("hello")}.should raise_error(ListDeserializer::SyntaxError) 21 | end 22 | 23 | it "can't deserialize string that doesn't end with a bracket" do 24 | proc {ListDeserializer.deserialize("[000000:")}.should raise_error(ListDeserializer::SyntaxError) 25 | end 26 | 27 | def check() 28 | serialized = ListSerializer.serialize(@list) 29 | deserialized = ListDeserializer.deserialize(serialized) 30 | deserialized.should == @list 31 | end 32 | 33 | it "can deserialize and empty list" do 34 | check 35 | end 36 | 37 | it "can deserialize a list with one element" do 38 | @list = ["hello"] 39 | check 40 | end 41 | 42 | it "can deserialize a list with two elements" do 43 | @list = ["hello", "bob"] 44 | check 45 | end 46 | 47 | it "can deserialize sublists" do 48 | @list = ["hello", ["bob", "micah"], "today"] 49 | check 50 | end 51 | 52 | it "can deserialize lists with multibyte strings" do 53 | @list = ["Köln"] 54 | check 55 | end 56 | 57 | it "can deserialize lists of strings that end with multibyte chars" do 58 | @list = ["Kö"] 59 | check 60 | end 61 | 62 | it "can deserialize lists with utf8" do 63 | @list = ["123456789012345", "Espa\357\277\275ol"] 64 | check 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /spec/list_executor_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require "statement_executor" 3 | require "list_executor" 4 | 5 | describe ListExecutor do 6 | before do 7 | @executor = ListExecutor.new 8 | @statements = [] 9 | @table = "
namebob
addrhere
" 10 | add_statement "i1", "import", "TestModule" 11 | add_statement "m1", "make", "test_slim", "TestSlim" 12 | end 13 | 14 | def get_result(id, result_list) 15 | pairs_to_map(result_list)[id] 16 | end 17 | 18 | def pairs_to_map(pairs) 19 | map = {} 20 | pairs.each {|pair| map[pair[0]] = pair[1]} 21 | map 22 | end 23 | 24 | def add_statement(*args) 25 | @statements << args 26 | end 27 | 28 | def check_results expectations 29 | results = @executor.execute(@statements) 30 | expectations.each_pair {|id, expected| 31 | get_result(id, results).should == expected 32 | } 33 | end 34 | 35 | it "can respond with OK to import" do 36 | check_results "i1"=>"OK" 37 | end 38 | 39 | it "can't execute an invalid operation" do 40 | add_statement "inv1", "invalidOperation" 41 | results = @executor.execute(@statements) 42 | get_result("inv1", results).should include(Statement::EXCEPTION_TAG+"message:<>" 48 | results = @executor.execute(@statements) 49 | get_result("id", results).should include(Statement::EXCEPTION_TAG+message) 50 | end 51 | 52 | it "can't call a method on an instance that doesn't exist" do 53 | add_statement "id", "call", "no_such_instance", "no_such_method" 54 | results = @executor.execute(@statements) 55 | get_result("id", results).should include(Statement::EXCEPTION_TAG+"message:<>") 56 | end 57 | 58 | it "should respond to an empty set of instructions with an empty set of results" do 59 | @executor.execute([]).length.should == 0 60 | end 61 | 62 | it "can make an instance given a fully qualified name in dot format" do 63 | @executor.execute([["m1", "make", "instance", "testModule.TestSlim"]]).should == [["m1", "OK"]] 64 | end 65 | 66 | it "can call a simple method in ruby form" do 67 | add_statement "id", "call", "test_slim", "return_string" 68 | 69 | check_results "m1" => "OK", "id"=>"string" 70 | end 71 | 72 | it "can call a simple method in ruby form" do 73 | add_statement "id", "call", "test_slim", "utf8" 74 | 75 | check_results "m1" => "OK", "id"=>"Espa\357\277\275ol" 76 | end 77 | 78 | 79 | it "can call a simple method in FitNesse form" do 80 | add_statement "id", "call", "test_slim", "returnString" 81 | 82 | check_results "m1"=>"OK", "id"=>"string" 83 | end 84 | 85 | it "will allow later imports to take precendence over early imports" do 86 | @statements.insert(0, ["i2", "import", "TestModule.ShouldNotFindTestSlimInHere"]) 87 | add_statement "id", "call", "test_slim", "return_string" 88 | check_results "m1"=>"OK", "id"=>"string" 89 | end 90 | 91 | it "can pass arguments to constructor" do 92 | add_statement "m2", "make", "test_slim_2", "TestSlimWithArguments", "3" 93 | add_statement "c1", "call", "test_slim_2", "arg" 94 | 95 | check_results "m2"=>"OK", "c1"=>"3" 96 | end 97 | 98 | it "can pass tables to constructor" do 99 | add_statement "m2", "make", "test_slim_2", "TestSlimWithArguments", @table 100 | add_statement "c1", "call", "test_slim_2", "name" 101 | add_statement "c2", "call", "test_slim_2", "addr" 102 | 103 | check_results "m2"=>"OK", "c1"=>"bob", "c2"=>"here" 104 | end 105 | 106 | it "can pass tables to functions" do 107 | add_statement "m2", "make", "test_slim_2", "TestSlimWithArguments", "nil" 108 | add_statement "c0", "call", "test_slim_2", "set_arg", @table 109 | add_statement "c1", "call", "test_slim_2", "name" 110 | add_statement "c2", "call", "test_slim_2", "addr" 111 | 112 | check_results "m2"=>"OK", "c1"=>"bob", "c2"=>"here" 113 | end 114 | 115 | it "can call a function more than once" do 116 | add_statement "c1", "call", "test_slim", "add", "x", "y" 117 | add_statement "c2", "call", "test_slim", "add", "a", "b" 118 | 119 | check_results "c1" => "xy", "c2" => "ab" 120 | end 121 | 122 | it "can assign the return value to a symbol" do 123 | add_statement "id1", "callAndAssign", "v", "test_slim", "add", "x", "y" 124 | add_statement "id2", "call", "test_slim", "echo", "$v" 125 | check_results "id1" => "xy", "id2" => "xy" 126 | end 127 | 128 | it "can replace multiple symbols in a single argument" do 129 | add_statement "id1", "callAndAssign", "v1", "test_slim", "echo", "Bob" 130 | add_statement "id2", "callAndAssign", "v2", "test_slim", "echo", "Martin" 131 | add_statement "id3", "call", "test_slim", "echo", "name: $v1 $v2" 132 | check_results "id3" => "name: Bob Martin" 133 | end 134 | 135 | it "should ignore '$' if what follows is not a symbol" do 136 | add_statement "id3", "call", "test_slim", "echo", "$v1" 137 | check_results "id3" => "$v1" 138 | end 139 | 140 | it "can pass and return a list" do 141 | l = ["1", "2"] 142 | add_statement "id", "call", "test_slim", "echo", l 143 | check_results "id"=> l 144 | end 145 | 146 | it "can pass a symbol in a list" do 147 | add_statement "id1", "callAndAssign", "v", "test_slim", "echo", "x" 148 | add_statement "id2", "call", "test_slim", "echo", ["$v"] 149 | check_results "id2" => ["x"] 150 | end 151 | 152 | it "can return null" do 153 | add_statement "id", "call", "test_slim", "null" 154 | check_results "id" => nil 155 | end 156 | 157 | it "can survive executing a syntax error" do 158 | add_statement "id", "call", "test_slim", "syntax_error" 159 | results = @executor.execute(@statements) 160 | get_result("id", results).should include(Statement::EXCEPTION_TAG) 161 | end 162 | 163 | it "can make a fixture from the name in a symbol" do 164 | add_statement "id1", "callAndAssign", "test_system", "test_slim", "echo", "TestChain" 165 | add_statement "id2", "make", "fixture_instance1", "$test_system" 166 | check_results "id2"=>"OK" 167 | end 168 | 169 | it "can make a fixture from a concatonated symbol" do 170 | add_statement "id1", "callAndAssign", "test_system", "test_slim", "echo", "Chain" 171 | add_statement "id2", "make", "fixture_instance1", "Test$test_system" 172 | check_results "id2"=>"OK" 173 | end 174 | 175 | it "can use a fixture method that returns a fixture object" do 176 | add_statement "id1", "callAndAssign", "object", "test_slim", "echo_object", "let_me_see", "Boogaloo" 177 | add_statement "id2", "call", "test_slim", "call_on", "let_me_see", "$object" 178 | check_results "id2" => "Boogaloo" 179 | end 180 | 181 | it "can use an instance that was stored as a symbol" do 182 | add_statement "id1", "callAndAssign", "test_slim_instance", "test_slim", "create_test_slim_with_string", "Boogaloo" 183 | add_statement "m2", "make", "test_slim", "$test_slim_instance" 184 | check_results "m2" => "OK" 185 | end 186 | 187 | end -------------------------------------------------------------------------------- /spec/list_serialzer_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 3 | require "list_serializer" 4 | 5 | describe ListSerializer do 6 | it "can serialize and empty list" do 7 | ListSerializer.serialize([]).should == "[000000:]" 8 | end 9 | 10 | it "can serialize a one item list" do 11 | ListSerializer.serialize(["hello"]).should == "[000001:000005:hello:]" 12 | end 13 | 14 | it "can serialize a two item list" do 15 | ListSerializer.serialize(["hello", "world"]).should == "[000002:000005:hello:000005:world:]" 16 | end 17 | 18 | it "can serialize a nested list" do 19 | ListSerializer.serialize([["element"]]).should == "[000001:000024:[000001:000007:element:]:]" 20 | end 21 | 22 | it "can serialize a list with a non-string" do 23 | ListSerializer.serialize([1]).should == "[000001:000001:1:]" 24 | end 25 | 26 | it "can serialize a null element" do 27 | ListSerializer.serialize([nil]).should == "[000001:000004:null:]" 28 | end 29 | 30 | it "can serialize a string with multibyte chars" do 31 | ListSerializer.serialize(["Köln"]).should == "[000001:000004:Köln:]" 32 | end 33 | 34 | it "can serialize a string with UTF8" do 35 | ListSerializer.serialize(["Español"]).should == "[000001:000007:Espa\xc3\xb1ol:]" 36 | end 37 | 38 | 39 | end 40 | -------------------------------------------------------------------------------- /spec/method_invocation_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 3 | require "statement_executor" 4 | 5 | describe StatementExecutor do 6 | before do 7 | @executor = StatementExecutor.new 8 | end 9 | context "Simple Method Invocations" do 10 | before do 11 | @executor.create("test_slim", "TestModule::TestSlim", []) 12 | @test_slim = @executor.instance("test_slim") 13 | end 14 | 15 | it "can call a method with no arguments" do 16 | @test_slim.should_receive(:no_args).with() 17 | @executor.call("test_slim", "no_args") 18 | end 19 | 20 | it "can't call a method that doesn't exist" do 21 | result = @executor.call("test_slim", "no_such_method") 22 | result.should include(Statement::EXCEPTION_TAG + "message:<>") 23 | end 24 | 25 | it "can call a method that returns a value" do 26 | @test_slim.should_receive(:return_value).and_return("arg") 27 | @executor.call("test_slim", "return_value").should == "arg" 28 | end 29 | 30 | it "can call a method that returns a value" do 31 | @test_slim.should_receive(:return_value).and_return("Español") 32 | val = @executor.call("test_slim", "return_value") 33 | val.should == "Español" 34 | val.length.should == 7 35 | end 36 | 37 | 38 | it "can call a method that takes an argument" do 39 | @test_slim.should_receive(:one_arg).with("arg") 40 | @executor.call("test_slim", "one_arg", "arg") 41 | end 42 | 43 | it "can't call a method on an instance that doesn't exist" do 44 | result = @executor.call("no_such_instance", "no_such_method") 45 | result.should include(Statement::EXCEPTION_TAG + "message:<>") 46 | end 47 | 48 | it "can replace symbol expressions with their values" do 49 | @executor.set_symbol("v", "bob") 50 | @executor.call("test_slim", "echo", "hi $v.").should == "hi bob." 51 | end 52 | 53 | it "can call a method on the @sut" do 54 | @test_slim.sut.should_receive(:sut_method).with() 55 | @executor.call("test_slim", "sut_method") 56 | end 57 | 58 | it "should call attributes on sut" do 59 | @executor.call("test_slim", "set_attribute", "a") 60 | @executor.call("test_slim", "attribute").should == "a" 61 | end 62 | end 63 | 64 | context "Method invocations using fixture with no sut" do 65 | before do 66 | @executor.create("test_slim", "TestModule::TestSlimWithNoSut", []); 67 | @test_slim = @executor.instance("test_slim") 68 | end 69 | 70 | it "can't call method that doesn't exist if no 'sut' exists" do 71 | result = @executor.call("test_slim", "no_such_method") 72 | result.should include(Statement::EXCEPTION_TAG + "message:<>") 73 | end 74 | end 75 | 76 | context "Method invocations when library instances have been created." do 77 | before do 78 | @executor.create("library_old", "TestModule::LibraryOld", []) 79 | @executor.create("library_new", "TestModule::LibraryNew", []) 80 | @library_old = @executor.instance("library_old") 81 | @library_new = @executor.instance("library_new") 82 | @executor.create("test_slim", "TestModule::TestSlim", []) 83 | @test_slim = @executor.instance("test_slim") 84 | end 85 | 86 | it "should throw normal exception if no such method is found." do 87 | result = @executor.call("test_slim", "no_such_method") 88 | result.should include(Statement::EXCEPTION_TAG + "message:<>") 89 | end 90 | 91 | it "should still call normal methods in fixture" do 92 | @test_slim.should_receive(:no_args).with() 93 | @executor.call("test_slim", "no_args") 94 | end 95 | 96 | it "should still call methods on the sut" do 97 | @test_slim.sut.should_receive(:sut_method).with() 98 | @executor.call("test_slim", "sut_method") 99 | end 100 | 101 | it "should call a specific method on library_old" do 102 | @library_old.should_receive(:method_on_library_old).with() 103 | @executor.call("test_slim", "method_on_library_old") 104 | end 105 | 106 | it "should call a specific method on library_new" do 107 | @library_new.should_receive(:method_on_library_new).with() 108 | @executor.call("test_slim", "method_on_library_new") 109 | end 110 | 111 | it "should call method on library_new but not on library_old" do 112 | @library_new.should_receive(:a_method).with() 113 | @library_old.should_not_receive(:a_method).with() 114 | @executor.call("test_slim", "a_method") 115 | end 116 | 117 | it "should call built-in library methods" do 118 | @executor.call("test_slim", "push_fixture").should == nil 119 | @executor.call("test_slim", "pop_fixture").should == nil 120 | end 121 | 122 | it "should translate getters and setters" do 123 | @executor.call("test_slim", "set_lib_attribute", "lemon") 124 | @executor.call("test_slim", "lib_attribute").should == "lemon" 125 | end 126 | 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/slim_helper_library_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require 'slim_helper_library' 3 | require 'statement_executor' 4 | 5 | SLIM_HELPER_LIBRARY_INSTANCE_NAME = "SlimHelperLibrary" 6 | ACTOR_INSTANCE_NAME = "scriptTableActor" 7 | 8 | describe SlimHelperLibrary do 9 | before() do 10 | @executor = StatementExecutor.new 11 | @executor.add_module("TestModule") 12 | @executor.create(ACTOR_INSTANCE_NAME, "TestSlim", ["0"]).should == "OK" 13 | @instance = @executor.instance(ACTOR_INSTANCE_NAME) 14 | @helper = SlimHelperLibrary.new(@executor) 15 | end 16 | 17 | it "can get current scriptTableActor" do 18 | @helper.get_fixture.should be @instance 19 | end 20 | 21 | it "can push and pop" do 22 | @helper.push_fixture 23 | @executor.create(ACTOR_INSTANCE_NAME, "TestSlim", ["1"]) 24 | @helper.get_fixture.should_not be @instance 25 | @helper.pop_fixture 26 | @helper.get_fixture.should be @instance 27 | end 28 | 29 | it "can push and pop many" do 30 | @helper.push_fixture 31 | @executor.create(ACTOR_INSTANCE_NAME, "TestChain", []).should == "OK" 32 | one = @executor.instance(ACTOR_INSTANCE_NAME) 33 | one.should_not be_nil 34 | @helper.push_fixture 35 | 36 | @executor.create(ACTOR_INSTANCE_NAME, "SimpleScript", []) 37 | two = @executor.instance(ACTOR_INSTANCE_NAME) 38 | one.should_not be two 39 | @helper.get_fixture.should be two 40 | 41 | @helper.pop_fixture 42 | @helper.get_fixture.should be one 43 | 44 | @helper.pop_fixture 45 | @helper.get_fixture.should be @instance 46 | 47 | end 48 | 49 | end 50 | 51 | 52 | #@Test 53 | #public void testSlimHelperLibraryIsStoredInSlimExecutor() throws Exception { 54 | # Object helperLibrary = caller.getInstance(SLIM_HELPER_LIBRARY_INSTANCE_NAME); 55 | # assertTrue(helperLibrary instanceof SlimHelperLibrary); 56 | #} 57 | # 58 | #@Test 59 | #public void testSlimHelperLibraryHasStatementExecutor() throws Exception { 60 | # SlimHelperLibrary helperLibrary = (SlimHelperLibrary) caller.getInstance(SLIM_HELPER_LIBRARY_INSTANCE_NAME); 61 | # assertSame(caller, helperLibrary.getStatementExecutor()); 62 | #} 63 | # 64 | #@Test 65 | #public void testSlimHelperLibraryCanPushAndPopFixture() throws Exception { 66 | # SlimHelperLibrary helperLibrary = (SlimHelperLibrary) caller.getInstance(SLIM_HELPER_LIBRARY_INSTANCE_NAME); 67 | # Object response = caller.create(ACTOR_INSTANCE_NAME, getTestClassName(), new Object[0]); 68 | # Object firstActor = caller.getInstance(ACTOR_INSTANCE_NAME); 69 | # 70 | # helperLibrary.pushFixture(); 71 | # 72 | # response = caller.create(ACTOR_INSTANCE_NAME, getTestClassName(), new Object[] {"1"}); 73 | # assertEquals("OK", response); 74 | # assertNotSame(firstActor, caller.getInstance(ACTOR_INSTANCE_NAME)); 75 | # 76 | # helperLibrary.popFixture(); 77 | # 78 | # assertSame(firstActor, caller.getInstance(ACTOR_INSTANCE_NAME)); 79 | #} 80 | # 81 | -------------------------------------------------------------------------------- /spec/socket_service_spec.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 3 | require 'socket_service' 4 | 5 | class SocketServiceTest < Test::Unit::TestCase 6 | def setup 7 | @port = 12345 8 | @ss = SocketService.new() 9 | @connections = 0 10 | end 11 | 12 | def testOneConnection 13 | @ss.serve(@port) {@connections += 1} 14 | connect(@port) 15 | @ss.close() 16 | assert_equal(1, @connections) 17 | end 18 | 19 | def testManyConnections 20 | @ss.serve(@port) {@connections += 1} 21 | 10.times {connect(@port)} 22 | @ss.close() 23 | assert_equal(10, @connections) 24 | assert_equal(0, @ss.pendingSessions) 25 | end 26 | 27 | def testSocketSend 28 | @ss.serve(@port) do |serverSocket| 29 | serverSocket.write("hi") 30 | end 31 | 32 | clientSocket = TCPSocket.open("localhost", @port) 33 | answer = clientSocket.gets 34 | clientSocket.close 35 | assert_equal("hi", answer) 36 | @ss.close() 37 | end 38 | 39 | # TEST FREEZES!!! 40 | # We should not be able to keep the service alive by hitting 41 | # it with connections after we close it? 42 | def _testCantKeepAliveByConnectingAfterClose 43 | #set up a service that waits for a message and then dies. 44 | @ss.serve(@port) do |serverSocket| 45 | message = serverSocket.gets 46 | end 47 | 48 | #s1 is a connection to that service. 49 | s1 = TCPSocket.open("localhost", @port) 50 | sleep(0.1) 51 | 52 | #now start closing the server in a separate thread. It cannot 53 | #finish closing until s1 completes. 54 | Thread.start {@ss.close} 55 | sleep(0.1) 56 | 57 | #try to connect to the dying server. 58 | s2=nil 59 | Thread.start {s2 = TCPSocket.open("localhost", @port)} 60 | sleep(0.1) 61 | assert_equal(nil, s2, "shouldn't have connected") 62 | assert_not_equal(nil, s1, "Should have connected") 63 | 64 | #Complete the s1 session. 65 | s1.write("testCloseRaceCondition"); 66 | s1.close 67 | 68 | #collect the pending s2 connection 69 | testThread = Thread.current 70 | @ss.serve(@port) {testThread.wakeup} 71 | Thread.stop 72 | assert_not_equal(nil, s2) 73 | s2.close 74 | @ss.close 75 | end 76 | 77 | def testSessionCount 78 | @ss.serve(@port) do |serverSocket| 79 | message = serverSocket.gets 80 | end 81 | 82 | s1 = nil; 83 | Thread.start {s1 = TCPSocket.open("localhost", @port)} 84 | sleep(0.2) 85 | assert_equal(1, @ss.pendingSessions); 86 | s1.write("testSessionCount"); 87 | s1.close 88 | sleep(0.2) 89 | assert_equal(0, @ss.pendingSessions) 90 | @ss.close 91 | end 92 | 93 | def connect(port) 94 | s = TCPSocket.open("localhost", @port) 95 | sleep(0.1) 96 | s.close 97 | end 98 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path(File.dirname(__FILE__) + "/../lib/rubyslim") 2 | require 'timeout' 3 | -------------------------------------------------------------------------------- /spec/statement_executor_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require "statement_executor" 3 | 4 | describe StatementExecutor do 5 | before do 6 | @executor = StatementExecutor.new 7 | end 8 | 9 | it "can split class names" do 10 | @executor.split_class_name("a::b::c").should == ["a", "b", "c"] 11 | end 12 | 13 | it "can convert module names to file names" do 14 | @executor.to_file_name("MyModuleName").should == "my_module_name" 15 | end 16 | 17 | it "can build the path name to a class" do 18 | @executor.make_path_to_class("ModuleOne::ModuleTwo::MyClass").should == "module_one/module_two/my_class" 19 | end 20 | 21 | it "can require a class" do 22 | @executor.add_module("MyModule") 23 | proc = proc {@executor.require_class("MyModule::MyClass")} 24 | proc.should raise_error(SlimError, /message:< "bar")) 29 | @executor.get_symbol("foo").foo.should == "bar" 30 | @executor.replace_symbol("$foo").foo.should == "bar" 31 | end 32 | 33 | describe "accessor translation" do 34 | class TestInstance 35 | attr_accessor :foo 36 | end 37 | 38 | before(:each) do 39 | @instance = TestInstance.new 40 | @executor.set_instance("test_instance", @instance) 41 | end 42 | 43 | it "should translate setters" do 44 | @executor.call("test_instance", "set_foo", "123") 45 | @instance.foo.should == "123" 46 | end 47 | 48 | end 49 | 50 | end -------------------------------------------------------------------------------- /spec/statement_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require "statement" 3 | 4 | describe Statement do 5 | before do 6 | @statement = Statement.new("") 7 | end 8 | 9 | it "can translate slim class names to ruby class names" do 10 | @statement.slim_to_ruby_class("myPackage.MyClass").should == "MyPackage::MyClass" 11 | @statement.slim_to_ruby_class("this.that::theOther").should == "This::That::TheOther" 12 | end 13 | 14 | it "can translate slim method names to ruby method names" do 15 | @statement.slim_to_ruby_method("myMethod").should == "my_method" 16 | end 17 | end -------------------------------------------------------------------------------- /spec/table_to_hash_converter_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require 'table_to_hash_converter' 3 | 4 | def shouldNotChange(string) 5 | TableToHashConverter.convert(string).should == string 6 | end 7 | 8 | describe TableToHashConverter do 9 | [ 10 | ["some string", "not a table"], 11 | ["blah", "incomplete table"], 12 | ["
hi
", "too few columns"], 13 | ["
himedlo
", "too many columns"] 14 | ].each do |string, reason| 15 | it "#{reason}: should not change '#{string}'" do 16 | shouldNotChange(string) 17 | end 18 | end 19 | end 20 | 21 | describe TableToHashConverter do 22 | [ 23 | ["
namebob
", {:name=>"bob"}], 24 | ["
name bob
", {:name=>"bob"}], 25 | ["
namebob
addrhere
", {:name=>'bob', :addr=>'here'}], 26 | ["
name
", {:name=>""}], 27 | ].each do |table, hash| 28 | it "should match #{table} to #{hash}" do 29 | TableToHashConverter.convert(table).should == hash 30 | end 31 | end 32 | end 33 | --------------------------------------------------------------------------------