├── .gitignore ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── pretentious ├── example.rb ├── lib ├── pretentious.rb └── pretentious │ ├── context.rb │ ├── deconstructor.rb │ ├── generator.rb │ ├── generator_base.rb │ ├── lazy_trigger.rb │ ├── minitest_generator.rb │ ├── recorded_proc.rb │ ├── rspec_generator.rb │ ├── trigger.rb │ ├── utils │ └── file_writer.rb │ └── version.rb ├── pretentious.gemspec ├── pretentious.yml ├── run_test.sh ├── sample.rb ├── spec ├── deconstructor_spec.rb ├── generated │ ├── fibonacci_spec.rb │ ├── m_d5_spec.rb │ ├── meme_spec.rb │ ├── test_class1_spec.rb │ ├── test_class2_spec.rb │ ├── test_class3_spec.rb │ ├── test_class4_spec.rb │ ├── test_class_for_auto_stub_spec.rb │ ├── test_class_for_mocks_spec.rb │ ├── test_class_r1_spec.rb │ └── test_class_r2_spec.rb ├── generator_spec.rb ├── lazy_trigger_spec.rb ├── minitest_generator_spec.rb ├── prententious_spec.rb ├── spec_helper.rb └── utils │ └── filewriter_spec.rb ├── test ├── generated │ ├── test_fibonacci.rb │ ├── test_m_d5.rb │ ├── test_meme.rb │ ├── test_test_class1.rb │ ├── test_test_class2.rb │ ├── test_test_class3.rb │ ├── test_test_class4.rb │ ├── test_test_class_for_mocks.rb │ ├── test_test_class_r1.rb │ └── test_test_class_r2.rb ├── minitest_helper.rb └── test_generator.rb └── test_classes.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.gem 4 | /.bundle/ 5 | /.yardoc 6 | /Gemfile.lock 7 | /_yardoc/ 8 | /coverage/ 9 | /doc/ 10 | /pkg/ 11 | /spec/reports/ 12 | /tmp/ 13 | *.bundle 14 | *.so 15 | *.o 16 | *.a 17 | mkmf.log 18 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - '*.gemspec' 4 | - 'Gemfile' 5 | - 'bin/**/*' 6 | - 'db/migrate/**/*' 7 | - 'db/schema.rb' 8 | - 'log/**/*' 9 | - 'tmp/**/*' 10 | - 'vendor/**/*' 11 | DisplayCopNames: true 12 | RunRailsCops: true 13 | 14 | Metrics/LineLength: 15 | Enabled: false 16 | 17 | Metrics/MethodLength: 18 | Enabled: false 19 | 20 | Metrics/ModuleLength: 21 | Enabled: false 22 | 23 | Style/Documentation: 24 | Enabled: false 25 | 26 | Style/ExtraSpacing: 27 | AllowForAlignment: true 28 | 29 | Style/MultilineOperationIndentation: 30 | Enabled: false 31 | 32 | Style/NegatedIf: 33 | Enabled: false 34 | 35 | Style/RegexpLiteral: 36 | Enabled: false 37 | 38 | Style/SingleSpaceBeforeFirstArg: 39 | Enabled: false 40 | 41 | Style/SpaceAroundOperators: 42 | Enabled: false 43 | 44 | Style/TrailingComma: 45 | EnforcedStyleForMultiline: 'comma' 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.1 (2015-12-20) 2 | Features: 3 | - Allow regex to be used in specifying target classes 4 | 5 | # 0.2 (2015-12-19) 6 | Bugfixes: 7 | - Fix constants not getting resolved 8 | 9 | Features: 10 | - support for pretentious.yml file 11 | - Strings can now be passed to spec_for 12 | 13 | # 0.1.12 (2015-12-12) 14 | Bugfixes: 15 | - fix duplicate specs inside minitest 16 | - Attempt to at least initialize unresolvable objects 17 | 18 | # 0.1.11 (2015-12-12) 19 | Bugfixes: 20 | - fix code style 21 | 22 | Features: 23 | - matchers should now prefer variable references in assertions if previously declared 24 | 25 | # 0.1.10 (2015-12-11) 26 | Bugfixes: 27 | - fix regression wherein method_missing is not handled properly 28 | 29 | Features: 30 | - Fix weird generated comments 31 | 32 | # 0.1.9 (2015-12-10) 33 | Bugfixes: 34 | - duplicate assertions are now removed 35 | 36 | Features: 37 | - use more realistic variable names for unamed variables 38 | 39 | # 0.1.8 (2015-12-07) 40 | Bugfixes: 41 | 42 | Features: 43 | - renamed ddtgen cli to pretentious 44 | - generated files are now placed on their own sub folder 45 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in pretentious.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Joseph Emmanuel Dayo 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/pretentious.svg)](https://badge.fury.io/rb/pretentious) [![Circle CI](https://circleci.com/gh/jedld/pretentious/tree/master.svg?style=svg)](https://circleci.com/gh/jedld/pretentious/tree/master) 2 | 3 | # Ruby::Pretentious 4 | Do you have a pretentious boss or development lead that pushes you to embrace BDD/TDD but for reasons hate it or them? here is a gem to deal with that. Now you CAN write your code first and then GENERATE tests later!! Yes you heard that right! To repeat, this gem allows you to write your code first and then automatically generate tests using the code you've written in a straightfoward manner! 5 | 6 | On a serious note, this gem allows you to generate tests template used for "characterization tests" much better than those generated by default for various frameworks. It is also useful for "recording" current behavior of existing components in order to prepare for refactoring. As a bonus it also exposes an Object Deconstructor which allows you, given any object, to obtain a ruby code on how it was created. 7 | 8 | ## Table of Contents 9 | 1. [Installation](#installation) 10 | 2. [Usage](#usage) 11 | 3. [Using the pretentious.yml file](#using-pretentious.yml) 12 | 4. [Declarative generation without using example files](#declarative-generation-without-using-example-files) 13 | 5. [Minitest](#minitest) 14 | 6. [Handling complex parameters and object constructors](#handling-complex-parameters-and-object-constructors) 15 | 7. [Capturing Exceptions](#capturing-exceptions) 16 | 8. [Auto Stubbing](#auto-stubbing) 17 | 9. [Object Deconstruction Utility](#object-deconstruction-utility) 18 | 10. [Using the Object deconstructor in rails](#using-the-object-deconstructor-in-rails) 19 | 11. [Things to do after](#things-to-do-after) 20 | 12. [Limitations](#limitations) 21 | 22 | ## Installation 23 | Add this line to your application's Gemfile: 24 | 25 | ```ruby 26 | gem 'pretentious' 27 | ``` 28 | 29 | And then execute: 30 | 31 | ``` 32 | $ bundle 33 | ``` 34 | 35 | Or install it yourself as: 36 | 37 | ``` 38 | $ gem install pretentious 39 | ``` 40 | 41 | ## Usage 42 | The are various ways to use the pretentious gem. First is using an example file. An example file is simply a ruby script that uses your target class in a way that you want to test. The pretentious gem will then analyze what methods are called, how it is created and generate test cases for it. 43 | 44 | The other way is using an init file to declare which classes to test and when. This is useful if you want to document how a class is used in an existing application. This is the prefered method for characterization testing on framework like rails. 45 | 46 | There are also various commandline options available. 47 | 48 | Please refer to the table of contents to skip directly to the specific use case. 49 | 50 | ### Using an example file 51 | First Create an example file (etc. example.rb) and define the classes that you want to test, if the class is already defined elsewhere just require them. Below is an example: 52 | 53 | ```ruby 54 | class Fibonacci 55 | 56 | def fib(n) 57 | return 0 if (n == 0) 58 | return 1 if (n == 1) 59 | return 1 if (n == 2) 60 | return fib(n - 1) + fib(n - 2) 61 | end 62 | 63 | def self.say_hello 64 | "hello" 65 | end 66 | 67 | end 68 | ``` 69 | 70 | Inside a Pretentious.spec_for(...) block. Just write boring code that instantiates an object as well as calls the methods like how you'd normally use them. Finally Specify the classes that you want to test: 71 | 72 | ```ruby 73 | class Fibonacci 74 | def fib(n) 75 | return 0 if (n == 0) 76 | return 1 if (n == 1) 77 | return 1 if (n == 2) 78 | return fib(n - 1) + fib(n - 2) 79 | end 80 | 81 | def self.say_hello 82 | "hello" 83 | end 84 | end 85 | 86 | Pretentious.spec_for(Fibonacci) do 87 | instance = Fibonacci.new 88 | 89 | (1..10).each do |n| 90 | instance.fib(n) 91 | end 92 | 93 | Fibonacci.say_hello 94 | end 95 | ``` 96 | 97 | Save your file and then switch to the terminal to invoke: 98 | 99 | ``` 100 | $ pretentious example.rb 101 | ``` 102 | 103 | This will automatically generate rspec tests for Fibonacci under /spec of the current working directory. 104 | 105 | You can actually invoke rspec at this point, but the tests will fail. Before you do that you should edit spec/spec_helper.rb and put the necessary requires and definitions there. 106 | 107 | For this example place the following into spec_helper.rb: 108 | 109 | ```ruby 110 | #inside spec_helper.rb 111 | class Fibonacci 112 | def fib(n) 113 | return 0 if (n == 0) 114 | return 1 if (n == 1) 115 | return 1 if (n == 2) 116 | return fib(n - 1) + fib(n - 2) 117 | end 118 | 119 | def self.say_hello 120 | "hello" 121 | end 122 | end 123 | ``` 124 | 125 | The generated spec file should look something like this 126 | 127 | ```ruby 128 | # This file was automatically generated by the pretentious gem 129 | require 'spec_helper' 130 | 131 | RSpec.describe Fibonacci do 132 | context 'Scenario 1' do 133 | before do 134 | @fixture = Fibonacci.new 135 | end 136 | 137 | it 'should pass current expectations' do 138 | n = 1 139 | n_1 = 2 140 | n_2 = 3 141 | n_3 = 4 142 | n_4 = 5 143 | # Fibonacci#fib when passed n = 1 should return 1 144 | expect(@fixture.fib(n)).to eq(n) 145 | 146 | # Fibonacci#fib when passed n = 2 should return 1 147 | expect(@fixture.fib(n_1)).to eq(n) 148 | 149 | # Fibonacci#fib when passed n = 3 should return 2 150 | expect(@fixture.fib(n_2)).to eq(n_1) 151 | 152 | # Fibonacci#fib when passed n = 4 should return 3 153 | expect(@fixture.fib(n_3)).to eq(n_2) 154 | 155 | # Fibonacci#fib when passed n = 5 should return 5 156 | expect(@fixture.fib(n_4)).to eq(n_4) 157 | end 158 | end 159 | 160 | it 'should pass current expectations' do 161 | # Fibonacci::say_hello should return 'hello' 162 | expect(Fibonacci.say_hello).to eq('hello') 163 | end 164 | end 165 | ``` 166 | 167 | awesome! Note that this approach is the only way to capture calls to class methods. 168 | 169 | You can also try this out with libraries like MD5 for example ... 170 | 171 | ```ruby 172 | #example.rb 173 | 174 | Pretentious.spec_for(Digest::MD5) do 175 | sample = "This is the digest" 176 | Digest::MD5.hexdigest(sample) 177 | end 178 | ``` 179 | 180 | You should get something like: 181 | 182 | ```ruby 183 | # This file was automatically generated by the pretentious gem 184 | require 'spec_helper' 185 | 186 | RSpec.describe Digest::MD5 do 187 | it 'should pass current expectations' do 188 | sample = 'This is the digest' 189 | # Digest::MD5::hexdigest when passed "This is the digest" should return '9f12248dcddeda976611d192efaaf72a' 190 | expect(Digest::MD5.hexdigest(sample)).to eq('9f12248dcddeda976611d192efaaf72a') 191 | end 192 | end 193 | ``` 194 | 195 | Note: If your test subject is already part of a larger application and would like to capture behavior in the manner that the application uses it, please look at [Declarative Generation](#declarative-generation-without-using-example-files). 196 | 197 | ### Commandline options 198 | If you already have an example file you can also use various command line options. 199 | 200 | passing --help will show these options 201 | 202 | ```ruby 203 | pretentious --help 204 | ``` 205 | 206 | The command below will generate tests for Meme using the provided example file. 207 | 208 | ```ruby 209 | pretentious -t Meme sample.rb 210 | pretentious -t $/Meme/ sample.rb # generate tests for all classes starting with Meme 211 | ``` 212 | 213 | Note: class methods are not captured using this method 214 | 215 | ### Using pretentious.yml 216 | If you run pretentious without passing an example file, it will look for pretentious.yml in the current location. Below is an example pretentious.yml file: 217 | 218 | ```YAML 219 | # Sample pretentious targets file 220 | # use $ to pass a regex or a ruby code that evals to a string 221 | targets: 222 | - class: Meme 223 | - class: $/^TestClass/ 224 | generators: 225 | - rspec 226 | - minitest 227 | examples: 228 | - sample.rb 229 | ``` 230 | 231 | It basically contains three parts, the target classes to generate test for, the generators to use (rspec/minitest) and the example files to run. Note that in this case, Pretentious.spec_for/minitest_for is not needed in the example file as the target classes are already specified in the targets section. 232 | 233 | ### Declarative generation without using example files 234 | Instead of using Pretentious.spec_for and wrapping the target code around a block, you may declaratively define when test generation should occur beforehand. This allows you to generate tests around code blocks without modifying source codes. This is useful for testing code embedded inside frameworks like rails where your "example" is already embedded inside existing code. 235 | 236 | For example lets say you want to generate tests for UserAuthenticaion that is used inside the login method inside the UsersController inside a Rails app. You'd simply define like below: 237 | 238 | ```ruby 239 | # initializers/pretentious.rb 240 | 241 | Pretentious.install_watcher # install watcher if not defined previously 242 | 243 | if Rails.env.test? #IMPORTANT don't run this when you don't need it! 244 | Pretentious.on(UsersController).method_called(:login).spec_for(UserAuthentication) #RSPEC 245 | Pretentious.on(UsersController).method_called(:login, :logout, ...).minitest_for(UserAuthentication) #minitest 246 | end 247 | 248 | # spec files will be written to the project root 249 | ``` 250 | 251 | The above code is equivalent to adding a spec_for inside the target method. 252 | 253 | Note that you must include the setup code in a place that you know runs before the target code block is run. For example, if you want to test a class that is used inside a controller in rails, it is best to put it in an initializer. It is also recommended to call Pretentious.install_watcher early on to be able to generate better fixtures. 254 | 255 | You can pass a block for manually handling the output, for example 256 | 257 | ```ruby 258 | # initializers/pretentious.rb 259 | Pretentious.install_watcher # install watcher if not defined previously 260 | 261 | if Rails.env.test? #IMPORTANT don't run this when you don't need it! 262 | Pretentious.on(UsersController).method_called(:login).spec_for(UserAuthentication) do |results| 263 | puts results[UserAuthentication][:output] 264 | end 265 | end 266 | 267 | # spec files will be written to the project root 268 | ``` 269 | 270 | or, use the FileWriter utility to write it to a file 271 | 272 | ```ruby 273 | # initializers/pretentious.rb 274 | Pretentious.install_watcher # install watcher if not defined previously 275 | 276 | if Rails.env.test? #IMPORTANT don't run this when you don't need it! 277 | Pretentious.on(UsersController).method_called(:login).spec_for(UserAuthentication) do |results| 278 | file_writer = Pretentious::FileWriter.new 279 | file_writer.write UserAuthenticaion, results[UserAuthenticaion] 280 | end 281 | end 282 | ``` 283 | 284 | IMPORTANT: If using rails or if it is part of a larger app, make sure to enable this only when you intend to generate specs! delete the initializer or comment the code out when it is not needed. 285 | 286 | If the class is still to be defined, you may pass the name of the class as a string instead. However, calls to class methods can't be reliably captured in this manner: 287 | 288 | ```ruby 289 | Pretentious.on(UsersController).method_called(:login).spec_for("UserAuthentication") 290 | ``` 291 | 292 | Of course, you call also pass a regex if you want to auto generate classes via some name convention: 293 | 294 | ```ruby 295 | Pretentious.on(UsersController).method_called(:login).spec_for(/^User/) 296 | ``` 297 | 298 | Classes starting with User will now be watched (e.g. UserController, UserProfile etc.) 299 | 300 | ## Minitest 301 | The minitest test framework is also supported, simply use Pretentious.minitest_for instead 302 | 303 | ```ruby 304 | Pretentious.minitest_for(Meme) do 305 | meme = Meme.new 306 | meme.i_can_has_cheezburger? 307 | meme.will_it_blend? 308 | end 309 | ``` 310 | 311 | outputs: 312 | 313 | ```ruby 314 | # This file was automatically generated by the pretentious gem 315 | require 'minitest_helper' 316 | require 'minitest/autorun' 317 | 318 | class MemeTest < Minitest::Test 319 | end 320 | 321 | class MemeScenario1 < MemeTest 322 | def setup 323 | @fixture = Meme.new 324 | end 325 | 326 | def test_current_expectation 327 | # Meme#i_can_has_cheezburger? should return 'OHAI!' 328 | assert_equal 'OHAI!', @fixture.i_can_has_cheezburger? 329 | 330 | # Meme#will_it_blend? should return 'YES!' 331 | assert_equal 'YES!', @fixture.will_it_blend? 332 | end 333 | end 334 | ``` 335 | 336 | ## Handling complex parameters and object constructors 337 | No need to do anything special, just do as what you would do normally and the pretentious gem will figure it out. see below for an example: 338 | 339 | ```ruby 340 | class TestClass1 341 | 342 | def initialize(message) 343 | @message = message 344 | end 345 | 346 | def print_message 347 | puts @message 348 | end 349 | 350 | def something_is_wrong 351 | raise StandardError.new 352 | end 353 | end 354 | 355 | class TestClass2 356 | def initialize(message) 357 | @message = {message: message} 358 | end 359 | 360 | def print_message 361 | puts @message[:message] 362 | end 363 | end 364 | 365 | class TestClass3 366 | def initialize(testclass1, testclass2) 367 | @class1 = testclass1 368 | @class2 = testclass2 369 | end 370 | 371 | def show_messages 372 | @class1.print_message 373 | @class2.print_message 374 | "awesome!!!" 375 | end 376 | end 377 | ``` 378 | 379 | We then instantiate the class using all sorts of weird parameters: 380 | 381 | ```ruby 382 | Pretentious.spec_for(TestClass3) do 383 | another_object = TestClass1.new("test") 384 | test_class_one = TestClass1.new({hello: "world", test: another_object, arr_1: [1,2,3,4,5, another_object], 385 | sub_hash: {yes: true, obj: another_object}}) 386 | test_class_two = TestClass2.new("This is message 2") 387 | 388 | class_to_test = TestClass3.new(test_class_one, test_class_two) 389 | class_to_test.show_messages 390 | end 391 | ``` 392 | 393 | Creating tests for TestClass3 should yield 394 | 395 | ```ruby 396 | require 'spec_helper' 397 | 398 | RSpec.describe TestClass3 do 399 | context 'Scenario 1' do 400 | before do 401 | another_object = TestClass1.new('test') 402 | b = { hello: 'world', test: another_object, arr_1: [1, 2, 3, 4, 5, another_object], sub_hash: { yes: true, obj: another_object } } 403 | testclass1 = TestClass1.new(b) 404 | testclass2 = TestClass2.new('This is message 2', nil) 405 | @fixture = TestClass3.new(testclass1, testclass2) 406 | end 407 | 408 | it 'should pass current expectations' do 409 | # TestClass3#show_messages should return 'awesome!!!' 410 | expect(@fixture.show_messages).to eq('awesome!!!') 411 | end 412 | end 413 | end 414 | ``` 415 | 416 | Note that creating another instance of TestClass3 will result in the creation of another Scenario 417 | 418 | ## Capturing Exceptions 419 | Exceptions thrown by method calls should generate the appropriate exception test case. Just make sure that you rescue inside your example file like below: 420 | 421 | ```ruby 422 | begin 423 | test_class_one.something_is_wrong 424 | rescue Exception=>e 425 | end 426 | ``` 427 | 428 | should generate the following in rspec 429 | 430 | ```ruby 431 | # TestClass1#something_is_wrong when passed should return StandardError 432 | expect { @fixture.something_is_wrong }.to raise_error 433 | ``` 434 | 435 | ## Auto stubbing 436 | Too lazy to generate rspec-mocks stubs? Let the Pretentious gem do it for you. 437 | 438 | Simply call the _stub method on a class and pass the classes you want to generate stubs for when passing calling spec_for or minitest_for (see below): 439 | 440 | ```ruby 441 | Pretentious.spec_for(TestClass._stub(ClassUsedByTestClass)) do 442 | instance = TestClass.new 443 | instance.method_that_uses_the_class_to_stub 444 | end 445 | ``` 446 | 447 | should auto generate the stub like this: 448 | 449 | ```ruby 450 | it 'should pass current expectations' do 451 | 452 | var_2181613400 = ["Hello Glorious world", "HI THERE!!!!"] 453 | 454 | allow_any_instance_of(ClassUsedByTestClass).to receive(:a_stubbed_method).and_return("Hello Glorious world") 455 | 456 | # TestClass#method_that_uses_the_class_to_stub should return ["Hello Glorious world", "HI THERE!!!!"] 457 | expect( @fixture.method_that_uses_the_class_to_stub ).to eq(["Hello Glorious world", "HI THERE!!!!"]) 458 | 459 | end 460 | ``` 461 | 462 | For minitest it returns something like: 463 | 464 | ```ruby 465 | class Scenario2 < TestTestClassForMocks 466 | def setup 467 | @fixture = TestClassForMocks.new 468 | end 469 | 470 | def test_current_expectation 471 | 472 | var_2174209040 = {val: 1, str: "hello world", message: "a message"} 473 | 474 | TestMockSubClass.stub_any_instance(:return_hash, var_2174209040) do 475 | #TestClassForMocks#method_with_usage3 when passed message = "a message" should return {:val=>1, :str=>"hello world", :message=>"a message"} 476 | assert_equal var_2174209040, @fixture.method_with_usage3("a message") 477 | 478 | end 479 | 480 | end 481 | end 482 | ``` 483 | 484 | Note: Stubbing on minitest requires the minitest-stub_any_instance gem. 485 | 486 | stubs that return different values every call are automatically detected an the appropriate rspec stub return is generated (similar to below): 487 | 488 | ```ruby 489 | allow_any_instance_of(TestMockSubClass).to receive(:increment_val).and_return(2, 3, 4, 5) 490 | ``` 491 | 492 | Yes, you can pass in multiple classes to be stubbed: 493 | 494 | ```ruby 495 | Pretentious.spec_for(TestClass._stub(ClassUsedByTestClass, AnotherClassUsedByTestClass, ....)) do 496 | instance = TestClass.new 497 | instance.method_that_uses_the_class_to_stub 498 | end 499 | ``` 500 | 501 | Note: different return values are only supported on RSpec for now 502 | 503 | ## Object Deconstruction Utility 504 | As Pretentious as the gem is, there are other uses other than generating tests specs. Tools are also available to deconstruct objects. Object deconstruction basically means that the components used to create and initialize an object are extracted and decomposed until only primitive types remain. The pretentious gem will also generate the necessary ruby code to create one from scratch. Below is an example: 505 | 506 | Given an instance of an activerecord base connection for example 507 | 508 | ```ruby 509 | ActiveRecord::Base.connection 510 | ``` 511 | 512 | running deconstruct on ActiveRecord::Base.connection 513 | 514 | ```ruby 515 | ActiveRecord::Base.connection._deconstruct_to_ruby 516 | ``` 517 | 518 | will generate the output below: 519 | 520 | ```ruby 521 | var_70301267513280 = # 522 | logger = ActiveSupport::Logger.new(var_70301267513280) 523 | connection_options = ["localhost", "root", "password", "test_db", nil, nil, 131074] 524 | config = {adapter: "mysql", encoding: "utf8", reconnect: false, database: "test_db", pool: 5, username: "root", password: "password", host: "localhost"} 525 | var_70301281665660 = ActiveRecord::ConnectionAdapters::MysqlAdapter.new(connection, logger, connection_options, config) 526 | ``` 527 | 528 | Note that 529 | 530 | ```ruby 531 | var_70301267513280 = # 532 | ``` 533 | 534 | because the pretentious gem was not able to capture its init arguments. 535 | 536 | ## How to use 537 | Simply call: 538 | 539 | ``` 540 | Pretentious.install_watcher 541 | ``` 542 | 543 | before all your objects are initalized. This will add the following methods to all objects: 544 | 545 | ```ruby 546 | object._deconstruct 547 | object._deconstruct_to_ruby 548 | ``` 549 | 550 | The _deconstruct method generates a raw deconstruction data structure used by the _deconstruct_to_ruby method. 551 | 552 | Of course _deconstruct_to_ruby generates the ruby code necessary to create the object! 553 | 554 | If you just want to watch certain objects, the watch method accepts a block: 555 | 556 | ```ruby 557 | some_other_class = SomeOtherClass.new("me") 558 | the_object = Pretentious.watch { 559 | MyTestClass.new("some parameter", some_other_class).new 560 | } 561 | 562 | the_object._deconstruct_to_ruby 563 | ``` 564 | 565 | Note that the class SomeOtherClass won't get decomposed. 566 | 567 | ## Using the Object deconstructor in rails 568 | In your Gemfile, add the pretentious gem. 569 | 570 | ```ruby 571 | group :test do 572 | gem 'pretentious' 573 | end 574 | ``` 575 | 576 | The do a bundle 577 | 578 | ``` 579 | $ bundle 580 | ``` 581 | 582 | Note: It is advisable to add it only in the test or development group! The way it logs objects would probably prevent anything from being GC'ed. 583 | 584 | For rails, including inside application.rb may be a good place to start: 585 | 586 | ```ruby 587 | #application.rb 588 | require File.expand_path('../boot', __FILE__) 589 | 590 | require 'rails/all' 591 | # Require the gems listed in Gemfile, including any gems 592 | # you've limited to :test, :development, or :production. 593 | Bundler.require(*Rails.groups) 594 | 595 | if Rails.env.test? || Rails.env.development? 596 | puts "watching new instances" 597 | Pretentious::Generator.watch_new_instances 598 | end 599 | 600 | module App 601 | class Application < Rails::Application 602 | # ..... stuff ...... 603 | 604 | end 605 | end 606 | ``` 607 | 608 | boot up the rails console and blow your mind: 609 | 610 | ``` 611 | $ rails c -e test 612 | watching new instances 613 | Loading test environment (Rails 4.2.4) 614 | irb: warn: can't alias context from irb_context. 615 | 2.2.0 :001 > puts ActiveRecord::Base.connection._deconstruct_to_ruby 616 | connection = # 617 | var_70301267513280 = # 618 | logger = ActiveSupport::Logger.new(var_70301267513280) 619 | connection_options = ["localhost", "root", "password", "app_test", nil, nil, 131074] 620 | config = {adapter: "mysql", encoding: "utf8", reconnect: false, database: "app_test", pool: 5, username: "root", password: "password", host: "localhost"} 621 | var_70301281665660 = ActiveRecord::ConnectionAdapters::MysqlAdapter.new(connection, logger, connection_options, config) 622 | => nil 623 | 2.2.0 :004 > w = User.where(id: 1) 624 | User Load (2.8ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = ? [["id", 1]] 625 | => # 626 | 2.2.0 :005 > w._deconstruct_to_ruby 627 | => "klass = User\nvar_70301317929300 = \"users\"\ntable = Arel::Table.new(var_70301317929300, klass)\nvar_70301339518120 = User::ActiveRecord_Relation.new(klass, table)\n" 628 | 2.2.0 :006 > puts w._deconstruct_to_ruby 629 | klass = User 630 | var_70301317929300 = "users" 631 | table = Arel::Table.new(var_70301317929300, klass) 632 | var_70301339518120 = User::ActiveRecord_Relation.new(klass, table) 633 | => nil 634 | 2.2.0 :007 > 635 | ``` 636 | 637 | Note: There are some objects it may fail to deconstruct properly because of certain [limitations](#limitations) or it may have failed to capture object creation early enough. 638 | 639 | ## Things to do after 640 | Since your tests are already written, and hopefully nobody notices its written by a machine, you may just leave it at that. Take note of the limitations though. 641 | 642 | But if lest your conscience suffers, it is best to go back to the specs and refine them, add more tests and behave like a bdd'er/tdd'er. 643 | 644 | ## Limitations 645 | Computers are bad at mind reading (for now) and they don't really know your expectation of "correctness", as such it assumes your code is correct and can only use equality based matchers. It can also only reliably match primitive data types, hashes, Procs and arrays to a degree. More complex expectations are unfortunately left for the humans to resolve. This is expected to improve in future versions of the pretentious gem. 646 | 647 | Procs that return a constant value will be resolved properly. However variable return values are currently still not generated properly will return a stub (future versions may use sourcify to resolve Procs for ruby 1.9) 648 | 649 | Also do note that it tries its best to determine how your fixtures are created, as well as the types of your parameters and does so by figuring out (recursively) the components that your object needs. Failure can happen during this process. 650 | 651 | Finally, Limit this gem for test environments only. 652 | 653 | ## Bugs 654 | This is the first iteration and a lot of broken things could happen 655 | 656 | ## Contributing 657 | 1. Fork it ([https://github.com/jedld/pretentious.git](https://github.com/jedld/pretentious.git)) 658 | 2. Create your feature branch (`git checkout -b my-new-feature`) 659 | 3. Commit your changes (`git commit -am 'Add some feature'`) 660 | 4. Push to the branch (`git push origin my-new-feature`) 661 | 5. Create a new Pull Request 662 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require "bundler/gem_tasks" 3 | 4 | RSpec::Core::RakeTask.new('spec') 5 | 6 | # If you want to make this the default task 7 | task :default => :spec 8 | task :test => :spec 9 | 10 | desc "Runs the minitest suite" 11 | task :minitest do 12 | $: << './test' 13 | Dir.glob('./test/**/test_*.rb').each { |file| require file} 14 | end 15 | -------------------------------------------------------------------------------- /bin/pretentious: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pretentious' 4 | require 'optparse' 5 | require 'ripper' 6 | require 'readline' 7 | require 'json' 8 | require 'fileutils' 9 | require 'yaml' 10 | 11 | def eval_example(filename) 12 | example_body = '' 13 | index = 0 14 | File.open(filename, "r") do |f| 15 | f.each_line do |line| 16 | example_body << "#{line}\n" 17 | end 18 | end 19 | 20 | eval(example_body, binding, filename, 1) 21 | end 22 | 23 | def write_output(output_folder, output_subfolder, last_results = nil) 24 | # collect results and write them 25 | puts "writing tests to #{output_folder}" 26 | filewriter = Pretentious::FileWriter.new(output_folder: output_folder, 27 | spec_output_folder: output_subfolder) 28 | filewriter.write_results(last_results || Pretentious.last_results) 29 | end 30 | 31 | def process_file(filename, output_folder, output_subfolder, last_results = nil) 32 | eval_example(filename) 33 | write_output(output_folder, output_subfolder, last_results) 34 | end 35 | 36 | def generate_specs(examples, generators, output_folder, output_subfolder) 37 | Pretentious.watch { 38 | examples.each do |f| 39 | puts "executing target #{f}" 40 | eval_example(f) 41 | end 42 | } 43 | 44 | generator_classes = generators.collect do |g| 45 | case g 46 | when 'rspec' 47 | Pretentious::RspecGenerator 48 | when 'minitest' 49 | Pretentious::MinitestGenerator 50 | end 51 | end 52 | 53 | per_spec = {} 54 | generator_classes.each do |generator_class| 55 | puts "generating #{generator_class.to_sym} tests" 56 | per_spec[generator_class.to_sym] = Pretentious::LazyTrigger.generate_for_class(generator_class) 57 | end 58 | per_spec = Pretentious.deep_merge(per_spec, Pretentious.last_results) 59 | write_output(output_folder, output_subfolder, per_spec) 60 | end 61 | 62 | output_folder = nil 63 | output_subfolder = nil 64 | targets = nil 65 | generators = "rspec" 66 | help = false 67 | 68 | # pretentious example.rb -t rspec -o rspec/ 69 | options = OptionParser.new do |o| 70 | o.banner = 71 | "Usage: pretentious FILENAME [options] # Generates tests using the specified example file\n" 72 | o.separator '' 73 | o.separator "options:" 74 | o.on('-h', '--help', 'show this help module') { help = true } 75 | o.on('-t=classnames', '--target=classnames', 76 | 'target class to generate tests for') { |b| targets = b} 77 | o.on('-g=GENERATORS', '--generators=GENERATORS', 'comma separated list of generators to use valid values rspec, minitest (defaults rspec)') { |b| generators = b} 78 | o.on('-n=namespace', '--namespace=NAMESPACE', 79 | 'sub folder to place the generated files in (defaults to generated)') { |b| output_subfolder = b } 80 | o.on('-o=OUTPUT_DIR', '--output-dir=OUTPUT_DIR', 81 | 'folder to place the files in -- defaults to spec (RSpec) or test (minitest)') { |b| output_folder = b} 82 | o.parse! 83 | end 84 | 85 | filename = ARGV[0] 86 | 87 | if help 88 | puts options 89 | exit(1) 90 | end 91 | if filename.nil? 92 | if File.exists?('pretentious.yml') 93 | targets_file = YAML.load_file('pretentious.yml') 94 | targets_file['targets'].each do |target| 95 | puts "generate for #{target['class']}" 96 | if target['class'].start_with?('$') 97 | target['class'][0] = '' 98 | Pretentious::LazyTrigger.new(eval(target['class']), {}) 99 | else 100 | Pretentious::LazyTrigger.new(target['class'], {}) 101 | end 102 | end 103 | 104 | examples = targets_file['examples'] 105 | generators = targets_file['generators'] 106 | 107 | generate_specs examples, generators, output_folder, output_subfolder 108 | else 109 | puts 'a target or an example file is required.' 110 | puts options 111 | exit(1) 112 | end 113 | else 114 | if !targets.nil? 115 | targets.split(',').each do |target| 116 | if target.start_with?('$') 117 | target[0] = '' 118 | Pretentious::LazyTrigger.new(eval(target), {}) 119 | else 120 | puts target 121 | Pretentious::LazyTrigger.new(target, {}) 122 | end 123 | end 124 | 125 | generate_specs filename.split(','), generators.split(','), output_folder, output_subfolder 126 | else 127 | process_file(filename, output_folder, output_subfolder) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /example.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << '.' 2 | 3 | require 'digest/md5' 4 | require_relative './test_classes.rb' 5 | 6 | [:spec_for, :minitest_for].each do |method| 7 | Pretentious.send(method, Fibonacci) do 8 | 9 | instance = Fibonacci.new 10 | 11 | (1..5).each do |n| 12 | instance.fib(n) 13 | end 14 | 15 | Fibonacci.say_hello 16 | 17 | end 18 | end 19 | 20 | [:spec_for, :minitest_for].each do |method| 21 | Pretentious.send(method, TestClass1, TestClass2, TestClass3, TestClass4) do 22 | another_object = TestClass1.new("test") 23 | test_class_one = TestClass1.new({hello: "world", test: another_object, arr_1: [1,2,3,4,5, another_object], 24 | sub_hash: {yes: true, obj: another_object}}) 25 | test_class_two = TestClass2.new("This is message 2", nil) 26 | 27 | class_to_test = TestClass3.new(test_class_one, test_class_two) 28 | class_to_test.show_messages 29 | 30 | class_to_test = TestClass3.new(test_class_one, test_class_two) 31 | class_to_test.show_messages 32 | 33 | class_to_test4 = TestClass4.new { 34 | another_object.message 35 | } 36 | 37 | test_class_one.set_block { |message| 38 | message 39 | } 40 | 41 | test_class_one.call_block { 42 | class_to_test4.message 43 | } 44 | 45 | message3 = "This is message 3" 46 | t = TestClass2.new(message3, nil) 47 | test_class_two = TestClass2.new(t, message3) 48 | test_class_two.test(message3) 49 | 50 | 51 | begin 52 | test_class_one.something_is_wrong 53 | rescue Exception=>e 54 | end 55 | 56 | test_class_one.just_returns_true 57 | 58 | test_class_object_return = TestClass1.new("Hello") 59 | test_class_object_return.return_self(another_object) 60 | 61 | test_class1 = TestClass1.new(another_object) 62 | test_class1.message 63 | end 64 | end 65 | 66 | [:spec_for, :minitest_for].each do |method| 67 | Pretentious.send(method, Digest::MD5) do 68 | sample = "This is the digest" 69 | Digest::MD5.hexdigest(sample) 70 | end 71 | end 72 | 73 | [:spec_for, :minitest_for].each do |m| 74 | Pretentious.send(m, TestClassForMocks._stub(TestMockSubClass)) do 75 | instance = TestClassForMocks.new 76 | instance.method_with_assign = "test" 77 | instance.method_with_usage 78 | instance.method_with_usage2 79 | instance.method_with_usage4 80 | 81 | instance2 = TestClassForMocks.new 82 | instance2.method_with_usage3("a message") 83 | end 84 | end 85 | 86 | Pretentious.spec_for(TestClassForAutoStub._stub(ClassUsedByTestClass)) do 87 | instance = TestClassForAutoStub.new 88 | instance.method_that_uses_the_class_to_stub 89 | end 90 | 91 | Pretentious.spec_for(TestClassForAutoStub._stub(ClassUsedByTestClass, AnotherClassUsedByTestClass)) do 92 | instance = TestClassForAutoStub.new 93 | instance.method_that_uses_the_class_to_stub 94 | end 95 | 96 | Pretentious.minitest_for(Meme) do 97 | meme = Meme.new 98 | meme.i_can_has_cheezburger? 99 | meme.will_it_blend? 100 | end 101 | -------------------------------------------------------------------------------- /lib/pretentious.rb: -------------------------------------------------------------------------------- 1 | require 'pretentious/version' 2 | require 'pretentious/context' 3 | require 'pretentious/generator_base' 4 | require 'pretentious/rspec_generator' 5 | require 'pretentious/minitest_generator' 6 | require 'pretentious/recorded_proc' 7 | require 'pretentious/generator' 8 | require 'binding_of_caller' 9 | require 'pretentious/deconstructor' 10 | require 'pretentious/trigger' 11 | require 'pretentious/lazy_trigger' 12 | require 'pretentious/utils/file_writer' 13 | 14 | Class.class_eval do 15 | def _stub(*classes) 16 | @stub_classes = classes 17 | self 18 | end 19 | 20 | def _get_stub_classes 21 | @stub_classes 22 | end 23 | end 24 | 25 | Regexp.class_eval do 26 | def _stub(*classes) 27 | @stub_classes = classes 28 | self 29 | end 30 | 31 | def _get_stub_classes 32 | @stub_classes 33 | end 34 | end 35 | 36 | String.class_eval do 37 | def _stub(*classes) 38 | @stub_classes = classes 39 | self 40 | end 41 | 42 | def _get_stub_classes 43 | @stub_classes 44 | end 45 | end 46 | 47 | Thread.class_eval do 48 | def _push_context(context) 49 | @_context ||= [] 50 | @_context << context 51 | end 52 | 53 | def _current_context 54 | @_context ||= [] 55 | @_context.last 56 | end 57 | 58 | def _all_context 59 | @_context ||= [] 60 | end 61 | 62 | def _pop_context 63 | @_context.pop 64 | end 65 | end 66 | 67 | # The main class to use for pretentious testing 68 | module Pretentious 69 | # misc convenience tools 70 | module DdtUtils 71 | def self.to_underscore(str) 72 | str.gsub(/(.)([A-Z])/, '\1_\2').downcase 73 | end 74 | end 75 | 76 | def self.spec_for(*klasses, &block) 77 | @spec_results ||= {} 78 | Pretentious::Generator.test_generator = Pretentious::RspecGenerator 79 | @spec_results.merge!(Pretentious::Generator 80 | .generate_for(*klasses, &block)) 81 | end 82 | 83 | def self.minitest_for(*klasses, &block) 84 | @minitest_results ||= {} 85 | Pretentious::Generator.test_generator = Pretentious::MinitestGenerator 86 | @minitest_results.merge!(Pretentious::Generator 87 | .generate_for(*klasses, &block)) 88 | end 89 | 90 | def self.write_results(output_folder = nil, output_subfolder = nil, last_results = nil) 91 | filewriter = Pretentious::FileWriter.new(output_folder: output_folder, 92 | spec_output_folder: output_subfolder) 93 | filewriter.write_results(last_results || Pretentious.last_results) 94 | end 95 | 96 | def self.clear_results 97 | @spec_results = {} 98 | @minitest_results = {} 99 | end 100 | 101 | def self.last_results 102 | { spec: @spec_results, minitest: @minitest_results } 103 | end 104 | 105 | def self.install_watcher 106 | Pretentious::Generator.watch_new_instances 107 | end 108 | 109 | def self.uninstall_watcher 110 | Pretentious::Generator.unwatch_new_instances 111 | end 112 | 113 | def self.value_ize(context, value) 114 | if value.is_a? String 115 | "'#{value}'" 116 | elsif value.is_a? Symbol 117 | ":#{value}" 118 | elsif value.is_a? Hash 119 | context.pick_name(value.object_id) 120 | elsif value.is_a? Pretentious::RecordedProc 121 | context.pick_name(value.target_proc.object_id) 122 | elsif value.nil? 123 | 'nil' 124 | elsif Pretentious::Deconstructor.primitive?(value) 125 | "#{value}" 126 | elsif context.variable_map && context.variable_map[value.object_id] 127 | context.pick_name(value.object_id) 128 | else 129 | "#{value}" 130 | end 131 | end 132 | 133 | def self.watch(&block) 134 | Pretentious::Generator.watch_new_instances 135 | result = block.call 136 | Pretentious::Generator.unwatch_new_instances 137 | result 138 | end 139 | 140 | def self.on(target_class) 141 | Pretentious::Trigger.new(target_class) 142 | end 143 | 144 | # deep merge without active support and does array deep merges as well 145 | def self.deep_merge(hash, second) 146 | merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 } 147 | hash.merge(second, &merger) 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/pretentious/context.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # Contains references to scoped variables 3 | class Context 4 | # list of variable names to use. i j are not there on purpose 5 | VARIABLE_NAMES = %w(a b c d e f g h k l m n o p q r s t u v w x y z) 6 | 7 | attr_accessor :declared_names, :variable_map, 8 | :previous_declarations 9 | 10 | def initialize(variable_map = {}, declared_names = {}, previous_declarations = {}) 11 | @declared_names = declared_names 12 | @variable_map = variable_map 13 | @previous_declarations = previous_declarations 14 | @current_name_dict = 0 15 | end 16 | 17 | def subcontext(declarations) 18 | previous_declarations = {} 19 | 20 | declarations.select { |d| d[:used_by] != :inline }.each do |d| 21 | previous_declarations[d[:id]] = pick_name(d[:id]) 22 | end 23 | 24 | Pretentious::Context.new(@variable_map, {}, previous_declarations) 25 | end 26 | 27 | def was_declared_previously?(object_id) 28 | @previous_declarations.key? object_id 29 | end 30 | 31 | def merge_variable_map(target_object) 32 | @variable_map.merge!(target_object._variable_map) if target_object.methods.include?(:_variable_map) && !target_object._variable_map.nil? 33 | end 34 | 35 | def register_instance_variable(object_id) 36 | @variable_map[object_id] = "@#{@previous_declarations[object_id]}" if @previous_declarations[object_id][0] != '@' 37 | end 38 | 39 | def register(object_id, name) 40 | @variable_map[object_id] = name 41 | end 42 | 43 | def dump 44 | puts "v-map #{@variable_map.inspect}" 45 | puts "d-map #{@declared_names.inspect}" 46 | puts "p-map #{@previous_declarations.inspect}" 47 | end 48 | 49 | def pick_name(object_id, value = :no_value_passed) 50 | var_name = map_name(object_id) 51 | return var_name if var_name 52 | 53 | var_name = "var_#{object_id}" 54 | 55 | if !@variable_map.nil? && @variable_map.include?(object_id) 56 | 57 | candidate_name = @variable_map[object_id].to_s 58 | if !@declared_names.include?(candidate_name) 59 | var_name = candidate_name 60 | @declared_names[candidate_name] = { count: 1, object_id: object_id } 61 | else 62 | 63 | if @declared_names[candidate_name][:object_id] == object_id 64 | var_name = candidate_name 65 | else 66 | new_name = "#{candidate_name}_#{@declared_names[candidate_name][:count]}" 67 | var_name = "#{new_name}" 68 | 69 | @declared_names[candidate_name][:count] += 1 70 | @declared_names[new_name] = { count: 1, object_id: object_id } 71 | end 72 | 73 | end 74 | else 75 | v = nil 76 | 77 | Kernel.loop do 78 | v = provide_name 79 | break if !@declared_names.key?(v) || v.nil? 80 | end 81 | 82 | var_name = v 83 | 84 | @declared_names[var_name] = { count: 1, object_id: object_id } 85 | end 86 | 87 | var_name 88 | end 89 | 90 | def value_of(value) 91 | Pretentious.value_ize(self, value) 92 | end 93 | 94 | def map_name(object_id) 95 | object_id_to_declared_names = {} 96 | 97 | if @declared_names 98 | @declared_names.each do |k, v| 99 | object_id_to_declared_names[v[:object_id]] = k if v 100 | end 101 | end 102 | 103 | # return immediately if already mapped 104 | return object_id_to_declared_names[object_id] if object_id_to_declared_names.include? object_id 105 | nil 106 | end 107 | 108 | private 109 | 110 | def provide_name 111 | if @current_name_dict < VARIABLE_NAMES.length 112 | VARIABLE_NAMES[@current_name_dict].tap { @current_name_dict += 1 } 113 | else 114 | nil 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/pretentious/deconstructor.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # Deconstructor - decompose an object into its parts 3 | class Deconstructor 4 | # Represents an unresolved class 5 | class UnResolved 6 | attr_accessor :target_object 7 | 8 | def initialize(object) 9 | @target_object = object 10 | end 11 | end 12 | 13 | # Represents a reference 14 | class Reference 15 | attr_accessor :tree 16 | 17 | def initialize(tree) 18 | @tree = tree 19 | end 20 | end 21 | 22 | def dfs_array(arr, refs) 23 | value = [] 24 | arr.each do |v| 25 | if Pretentious::Deconstructor.primitive?(v) 26 | value << v 27 | elsif v.is_a? Hash 28 | value << dfs_hash(v, refs) 29 | elsif v.is_a? Array 30 | value << dfs_array(v, refs) 31 | elsif v.is_a? Reference 32 | refs << v.tree[:id] 33 | value << Reference.new(dfs(v.tree)) 34 | elsif value << v 35 | end 36 | end 37 | value 38 | end 39 | 40 | def dfs_hash(hash, refs) 41 | value = {} 42 | hash.each do |k, v| 43 | if Pretentious::Deconstructor.primitive?(v) 44 | value[k] = v 45 | elsif v.is_a? Hash 46 | value[k] = dfs_hash(v, refs) 47 | elsif v.is_a? Array 48 | value[k] = dfs_array(v, refs) 49 | elsif v.is_a? Reference 50 | refs << v.tree[:id] 51 | value[k] = Reference.new(dfs(v.tree)) 52 | else 53 | value[k] = v 54 | end 55 | end 56 | value 57 | end 58 | 59 | def dfs(tree) 60 | if !tree.is_a? Hash 61 | value = tree 62 | definition = { id: value.object_id, 63 | class: tree.class, 64 | value: value, 65 | used_by: [] } 66 | unless @dependencies.include? value.object_id 67 | @dependencies[value.object_id] = definition 68 | @declaration_order << definition 69 | end 70 | value.object_id 71 | else 72 | ref = [] 73 | 74 | definition = { id: tree[:id], 75 | class: tree[:class], 76 | params_types: tree[:params_types], 77 | used_by: [] } 78 | 79 | if tree[:class] == Hash 80 | definition[:value] = dfs_hash(tree[:composition], ref) 81 | elsif tree[:class] == Array 82 | definition[:value] = dfs_array(tree[:composition], ref) 83 | elsif tree[:class] == Pretentious::RecordedProc 84 | definition[:recorded_proc] = tree[:recorded_proc] 85 | 86 | if !tree[:composition].nil? 87 | ref << dfs(tree[:composition]) 88 | else 89 | dfs(tree[:composition]) 90 | end 91 | 92 | elsif tree[:composition].is_a? Array 93 | tree[:composition].each { |t| ref << dfs(t) } 94 | else 95 | ref << dfs(tree[:composition]) 96 | end 97 | 98 | # evaluate given block composition 99 | ref << dfs(tree[:block]) if tree[:block] 100 | 101 | definition[:ref] = ref 102 | 103 | unless @dependencies.include? tree[:id] 104 | @declaration_order << definition 105 | @dependencies[tree[:id]] = definition 106 | 107 | ref.each { |r| @dependencies[r][:used_by] << definition } 108 | end 109 | 110 | tree[:id] 111 | end 112 | end 113 | 114 | def update_ref_counts(params_arr, method_call) 115 | params_arr.each do |p| 116 | if @dependencies.include? p.object_id 117 | @dependencies[p.object_id][:used_by] << method_call 118 | end 119 | end 120 | end 121 | 122 | def inline 123 | @dependencies.each do |id, definition| 124 | next if definition[:used_by].size != 1 125 | next if !definition.include?(:value) || !self.class.primitive?(definition[:value]) 126 | ref = definition[:used_by][0] 127 | definition[:used_by] = :inline 128 | references = ref[:ref] 129 | if references 130 | new_ref = references.collect { |c| c == id ? definition : c } 131 | ref[:ref] = new_ref 132 | end 133 | end 134 | end 135 | 136 | # creates a tree on how the object was created 137 | def build_tree(target_object) 138 | tree = { class: get_test_class(target_object), id: target_object.object_id, composition: [] } 139 | if target_object.is_a? Array 140 | tree[:composition] = deconstruct_array(target_object) 141 | elsif target_object.is_a? Hash 142 | tree[:composition] = deconstruct_hash(target_object) 143 | elsif target_object.is_a? Pretentious::RecordedProc 144 | tree[:composition] = deconstruct_proc(target_object) 145 | tree[:given_block] = target_object.given_block? 146 | tree[:recorded_proc] = target_object 147 | tree[:id] = target_object.target_proc.object_id 148 | tree[:block_params] = self.class.block_param_names(target_object) 149 | elsif target_object.methods.include? :_get_init_arguments 150 | args = target_object._get_init_arguments 151 | if args.nil? 152 | if self.class.primitive?(target_object) 153 | tree[:composition] = target_object 154 | elsif target_object.class == File 155 | tree[:composition] << build_tree(target_object.path) 156 | else 157 | tree[:composition] = UnResolved.new(target_object) 158 | end 159 | else 160 | tree[:params_types] = args[:params_types] 161 | args[:params].each { |p| tree[:composition] << build_tree(p) } 162 | 163 | tree[:block] = build_tree(args[:block]) unless args[:block].nil? 164 | end 165 | 166 | else 167 | tree[:composition] = target_object 168 | end 169 | tree 170 | end 171 | 172 | def deconstruct(method_call_collection, *target_objects) 173 | @declaration_order = [] 174 | @dependencies = {} 175 | 176 | target_objects.each do |target_object| 177 | tree = build_tree target_object 178 | dfs(tree) 179 | end 180 | 181 | method_call_collection.each do |m| 182 | update_ref_counts(m[:params], m) 183 | end 184 | 185 | inline 186 | 187 | { declaration: @declaration_order, dependency: @dependencies } 188 | end 189 | 190 | def generate_declarations(context, method_call_collection, *target_objects) 191 | target_objects.each do |target_object| 192 | context.merge_variable_map(target_object) 193 | end 194 | deconstruct method_call_collection, *target_objects 195 | end 196 | 197 | def build_output(context, indentation_level, declarations) 198 | output_buffer = '' 199 | indentation = '' 200 | indentation_level.times { indentation << ' ' } 201 | declarations[:declaration].select { |d| d[:used_by] != :inline }.each do |d| 202 | if !context.was_declared_previously?(d[:id]) 203 | var_name = context.pick_name(d[:id]) 204 | output_buffer << "#{indentation}#{var_name} = #{construct(context, d, indentation)}\n" 205 | elsif context.was_declared_previously?(d[:id]) 206 | context.register_instance_variable(d[:id]) 207 | end 208 | end 209 | output_buffer 210 | end 211 | 212 | def deconstruct_to_ruby(context, indentation_level = 0, *target_objects) 213 | declarations, _dependencies = generate_declarations context, [], *target_objects 214 | build_output(context, indentation_level, declarations) 215 | end 216 | 217 | def self.primitive?(value) 218 | value.is_a?(String) || value.is_a?(Fixnum) || value.is_a?(TrueClass) || value.is_a?(FalseClass) || 219 | value.is_a?(NilClass) || value.is_a?(Symbol) || value.is_a?(Class) 220 | end 221 | 222 | def self.block_param_names(proc) 223 | parameters_to_join = [] 224 | 225 | parameters = proc.target_proc.parameters 226 | 227 | parameters.each { |p| parameters_to_join << p[1].to_s } 228 | parameters_to_join 229 | end 230 | 231 | def self.block_params_generator(proc, separator = '|') 232 | if proc.target_proc.parameters.size > 0 233 | return "#{separator}#{block_param_names(proc).join(', ')}#{separator}" 234 | end 235 | 236 | '' 237 | end 238 | 239 | def proc_to_ruby(context, proc, indentation = '') 240 | output_buffer = '' 241 | output_buffer << "proc { #{self.class.block_params_generator(proc)}\n" 242 | output_buffer << self.class.proc_body(context, proc, indentation) 243 | output_buffer << "#{indentation}}\n" 244 | output_buffer 245 | end 246 | 247 | def self.proc_body(context, proc, indentation = '') 248 | if proc.return_value.size == 1 249 | "#{indentation} #{context.value_of(proc.return_value[0])}\n" 250 | else 251 | "#{indentation} \# Variable return values ... can't figure out what goes in here...\n" 252 | end 253 | end 254 | 255 | def deconstruct_array(array) 256 | composition = [] 257 | array.each do |v| 258 | if Pretentious::Deconstructor.primitive?(v) 259 | composition << v 260 | elsif v.is_a? Hash 261 | composition << deconstruct_hash(v) 262 | elsif v.is_a? Array 263 | composition << deconstruct_array(v) 264 | else 265 | composition << Reference.new(build_tree(v)) 266 | end 267 | end 268 | composition 269 | end 270 | 271 | def deconstruct_hash(hash) 272 | composition = {} 273 | hash.each do |k, v| 274 | if Pretentious::Deconstructor.primitive?(v) 275 | composition[k] = v 276 | elsif v.is_a? Hash 277 | composition[k] = deconstruct_hash(v) 278 | elsif v.is_a? Array 279 | composition[k] = deconstruct_array(v) 280 | else 281 | composition[k] = Reference.new(build_tree(v)) 282 | end 283 | end 284 | composition 285 | end 286 | 287 | def deconstruct_proc(proc) 288 | return nil if proc.return_value.size != 1 289 | return build_tree(proc.return_value[0]) unless proc.return_value[0].nil? 290 | end 291 | 292 | def get_test_class(target_object) 293 | target_object.respond_to?(:test_class) ? target_object.test_class : target_object.class 294 | end 295 | 296 | private 297 | 298 | def output_array(context, arr) 299 | output_buffer = '[' 300 | array_elements = [] 301 | arr.each do |v| 302 | value = Pretentious.value_ize(context, v) 303 | if v.is_a? Hash 304 | value = output_hash(context, v) 305 | elsif v.is_a? Array 306 | value = output_array(context, v) 307 | elsif v.is_a? Reference 308 | value = context.pick_name(v.tree) 309 | end 310 | array_elements << value 311 | end 312 | output_buffer << array_elements.join(', ') 313 | output_buffer << ']' 314 | output_buffer 315 | end 316 | 317 | def output_hash(context, hash) 318 | output_buffer = '{ ' 319 | hash_elements = [] 320 | hash.each do |k, v| 321 | value = context.value_of(v) 322 | if v.is_a? Hash 323 | value = output_hash(context, v) 324 | elsif v.is_a? Array 325 | value = output_array(context, v) 326 | elsif v.is_a? Reference 327 | value = context.pick_name(v.tree) 328 | end 329 | 330 | if k.is_a? Symbol 331 | hash_elements << "#{k}: #{value}" 332 | else 333 | hash_elements << "#{context.value_of(k)} => #{value}" 334 | end 335 | end 336 | output_buffer << hash_elements.join(', ') 337 | output_buffer << ' }' 338 | output_buffer 339 | end 340 | 341 | def construct(context, definition, indentation = '') 342 | if definition.include? :value 343 | if definition[:value].is_a? Hash 344 | output_hash(context, definition[:value]) 345 | elsif definition[:value].is_a? Array 346 | output_array(context, definition[:value]) 347 | elsif definition[:value].is_a? UnResolved 348 | "#{definition[:value].target_object.class.to_s}.new # parameters unresolvable. The watcher needs to be installed before this object is created" 349 | else 350 | context.value_of(definition[:value]) 351 | end 352 | elsif definition[:class] == Pretentious::RecordedProc 353 | proc_to_ruby(context, definition[:recorded_proc], indentation) 354 | else 355 | params = [] 356 | if definition[:ref] && definition[:ref].size > 0 357 | 358 | params_types = definition[:params_types] 359 | definition[:ref].each_with_index do |v, index| 360 | type = :param 361 | if params_types && params_types[index] 362 | type = params_types[index][0] 363 | end 364 | 365 | # to inline? 366 | if v.is_a? Hash 367 | params << context.value_of(v[:value]) 368 | else 369 | params << (type == :block ? "&#{context.pick_name(v)}" : context.pick_name(v)) 370 | end 371 | end 372 | "#{definition[:class]}.new(#{params.join(', ')})" 373 | else 374 | "#{definition[:class]}.new" 375 | end 376 | 377 | end 378 | end 379 | 380 | end 381 | end 382 | -------------------------------------------------------------------------------- /lib/pretentious/generator.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # A class that generates specs by analyzing how an object is used 3 | class Generator 4 | 5 | def self.test_generator=(generator) 6 | @test_generator = generator 7 | end 8 | 9 | def self.test_generator 10 | @test_generator || Pretentious::RspecGenerator 11 | end 12 | 13 | def self.impostor_for(module_space, klass) 14 | new_standin_klass = Class.new 15 | name = klass.name 16 | 17 | # return if already an impostor 18 | return klass if klass.respond_to?(:test_class) 19 | 20 | module_space.const_set "#{name.split('::').last}Impostor", new_standin_klass 21 | 22 | new_standin_klass.class_eval(" 23 | def setup_instance(*args, &block) 24 | @_instance = #{klass.name}_ddt.new(*args, &block) 25 | end 26 | ") 27 | 28 | new_standin_klass.class_eval(" 29 | class << self 30 | def _get_standin_class 31 | #{new_standin_klass} 32 | end 33 | 34 | def test_class 35 | #{klass.name} 36 | end 37 | 38 | def _current_old_class 39 | #{klass.name}_ddt 40 | end 41 | end 42 | ") 43 | 44 | new_standin_klass.class_exec do 45 | 46 | def initialize(*args, &block) 47 | #check for special invocations 48 | an_instance = if args[0] == :_no_init 49 | _cmd, an_instance = args.shift(2) 50 | an_instance 51 | else 52 | nil 53 | end 54 | @_instance_init = { object_id: object_id, params: [], block: nil } 55 | 56 | self.class.replace_procs_with_recorders(args) 57 | 58 | @_instance_init[:params] = args 59 | recorded_proc = block ? RecordedProc.new(block, true) : nil 60 | 61 | @_instance_init[:block] = recorded_proc 62 | 63 | @_instance = if an_instance 64 | an_instance 65 | else 66 | setup_instance(*args, &recorded_proc) 67 | end 68 | 69 | param_types = @_instance.method(:initialize).parameters 70 | @_instance_init[:params_types] = param_types 71 | 72 | @_method_calls = [] 73 | @_method_calls_by_method = {} 74 | @_methods_for_test = [] 75 | @_let_variables = {} 76 | 77 | @_init_let_variables = {} 78 | 79 | caller_context = binding.of_caller(2) 80 | v_locals = caller_context.eval('local_variables') 81 | 82 | v_locals.each do |v| 83 | begin 84 | variable_value = caller_context.eval("#{v.to_s}") 85 | @_init_let_variables[variable_value.object_id] = v 86 | rescue NoMethodError 87 | end 88 | end 89 | 90 | args.each_with_index do |a, index| 91 | @_init_let_variables[a.object_id] = param_types[index][1].to_s if param_types.size == 2 92 | end 93 | 94 | self.class._add_instances(self) 95 | end 96 | 97 | def _init_arguments 98 | @_instance_init 99 | end 100 | 101 | def _get_init_arguments 102 | @_instance_init 103 | end 104 | 105 | def test_class 106 | @_instance.class 107 | end 108 | 109 | def include_for_tests(method_list = []) 110 | @_methods_for_test += method_list 111 | end 112 | 113 | def let_variables 114 | @_let_variables 115 | end 116 | 117 | def init_let_variables 118 | @_init_let_variables.dup 119 | end 120 | 121 | def method_calls_by_method 122 | @_method_calls_by_method 123 | end 124 | 125 | def method_calls 126 | @_method_calls 127 | end 128 | 129 | def to_s 130 | @_instance.to_s 131 | end 132 | 133 | def impostor? 134 | true 135 | end 136 | 137 | def ==(other) 138 | @_instance == other 139 | end 140 | 141 | def kind_of?(klass) 142 | @_instance.is_a? klass 143 | end 144 | 145 | def methods 146 | @_instance.methods + [:method_calls] 147 | end 148 | 149 | # don't really freeze 150 | def freeze 151 | @_instance 152 | end 153 | 154 | def hash 155 | @instance.hash 156 | end 157 | 158 | def inspect 159 | @_instance.inspect 160 | end 161 | 162 | def is_a?(something) 163 | @_instance.is_a? something 164 | end 165 | 166 | def instance_variable_get(sym) 167 | @_instance.instance_variable_get(sym) 168 | end 169 | 170 | def instance_variable_set(sym, val) 171 | @_instance.instance_variable_get(sym, val) 172 | end 173 | 174 | class << self 175 | 176 | def impostor? 177 | true 178 | end 179 | 180 | def replace_procs_with_recorders(args) 181 | (0..args.size).each do |index| 182 | if args[index].kind_of? Proc 183 | args[index] = Pretentious::RecordedProc.new(args[index]) {} 184 | end 185 | end 186 | end 187 | 188 | def _set_is_stub 189 | @_is_stub = true 190 | end 191 | 192 | def _is_stub? 193 | @_is_stub ||= false 194 | @_is_stub 195 | end 196 | 197 | def _add_instances(instance) 198 | @_instances ||= [] 199 | @_instances << instance unless @_instances.include? instance 200 | end 201 | 202 | def let_variables 203 | @_let_variables ? @_let_variables.dup : {} 204 | end 205 | 206 | def method_calls_by_method 207 | @_method_calls_by_method 208 | end 209 | 210 | def method_calls 211 | @_method_calls 212 | end 213 | 214 | def _add_instances(instance) 215 | @_instances ||= [] 216 | @_instances << instance unless @_instances.include? instance 217 | end 218 | 219 | def _instances 220 | @_instances 221 | end 222 | 223 | def instance_methods 224 | methods = super 225 | test_class.instance_methods + methods 226 | end 227 | 228 | def _call_method(target, method_sym, *arguments, &block) 229 | klass = nil 230 | begin 231 | klass = _get_standin_class 232 | rescue NameError=>e 233 | result = nil 234 | target.instance_exec do 235 | result = if @_instance.methods.include? method_sym 236 | @_instance.send(method_sym, *arguments, &block) 237 | else 238 | @_instance.send(:method_missing, method_sym, 239 | *arguments, &block) 240 | end 241 | end 242 | return result 243 | end 244 | 245 | caller_context = binding.of_caller(2) 246 | 247 | is_stub = _is_stub? 248 | 249 | target.instance_exec do 250 | @_method_calls ||= [] 251 | @_method_calls_by_method ||= {} 252 | @_methods_for_test ||= [] 253 | @_let_variables ||= {} 254 | 255 | v_locals = caller_context.eval('local_variables') 256 | 257 | v_locals.each do |v| 258 | begin 259 | variable_value = caller_context.eval("#{v}") 260 | @_let_variables[variable_value.object_id] = v 261 | rescue NoMethodError 262 | end 263 | end 264 | 265 | klass.replace_procs_with_recorders(arguments) 266 | 267 | info_block = {} 268 | info_block[:method] = method_sym 269 | info_block[:params] = arguments 270 | 271 | recorded_proc = block ? RecordedProc.new(block, true) : nil 272 | 273 | info_block[:block] = recorded_proc 274 | 275 | begin 276 | 277 | unless is_stub 278 | current_context = { calls: [] } 279 | info_block[:context] = current_context 280 | 281 | Thread.current._push_context(current_context) 282 | end 283 | 284 | if @_instance.methods.include? method_sym 285 | info_block[:names] = @_instance.method(method_sym).parameters 286 | result = @_instance.send(method_sym, *arguments, &recorded_proc) 287 | else 288 | result = @_instance.send(:method_missing, method_sym, *arguments, &recorded_proc) 289 | end 290 | 291 | Thread.current._pop_context unless is_stub 292 | 293 | # methods that end with = are a special case with return values 294 | if method_sym.to_s.end_with? '=' 295 | info_block[:result] = arguments[0] 296 | else 297 | info_block[:result] = result 298 | end 299 | rescue StandardError => e 300 | info_block[:result] = e 301 | end 302 | 303 | if is_stub 304 | info_block[:class] = test_class 305 | Thread.current._all_context.each do |mock_context| 306 | mock_context[:calls] << info_block if mock_context 307 | end 308 | end 309 | 310 | @_method_calls << info_block 311 | 312 | if @_method_calls_by_method[method_sym].nil? 313 | @_method_calls_by_method[method_sym] = [] 314 | end 315 | 316 | @_method_calls_by_method[method_sym] << info_block 317 | fail e if e.is_a? Exception 318 | result 319 | end 320 | end 321 | end 322 | end 323 | 324 | new_standin_klass.class_exec do 325 | def method_missing(method_sym, *arguments, &block) 326 | self.class._call_method(self, method_sym, *arguments, &block) 327 | end 328 | 329 | class << self 330 | def method_missing(method_sym, *arguments, &block) 331 | _add_instances(self) 332 | @_instance = _current_old_class 333 | _call_method(self, method_sym, *arguments, &block) 334 | end 335 | 336 | def const_missing(sym) 337 | _add_instances(self) 338 | @_instance = _current_old_class 339 | @_instance.const_get(sym) 340 | end 341 | end 342 | end 343 | 344 | new_standin_klass 345 | end 346 | 347 | def self.denamespace(klass) 348 | klass_name_parts = klass.name.split('::') 349 | last_part = klass_name_parts.pop 350 | 351 | module_space = Object 352 | 353 | if klass_name_parts.size > 0 354 | klass_name_parts.each do |part| 355 | module_space = module_space.const_get(part) 356 | end 357 | end 358 | 359 | [last_part, module_space] 360 | end 361 | 362 | def self.replace_class(klass, stub = false) 363 | last_part, module_space = denamespace(klass) 364 | new_standin_klass = impostor_for module_space, klass 365 | 366 | new_standin_klass._set_is_stub if stub 367 | 368 | module_space.send(:remove_const, last_part.to_sym) 369 | module_space.const_set("#{last_part}_ddt", klass) 370 | module_space.const_set("#{last_part}", new_standin_klass) 371 | 372 | [module_space, klass, last_part, new_standin_klass] 373 | end 374 | 375 | def self.restore_class(module_space, klass, last_part) 376 | module_space.send(:remove_const, "#{last_part}Impostor".to_sym) if Object.const_defined?("#{last_part}Impostor") 377 | module_space.send(:remove_const, "#{last_part}".to_sym) if Object.const_defined?(last_part) 378 | module_space.const_set(last_part, klass) 379 | module_space.send(:remove_const, "#{last_part}_ddt".to_sym) if Object.const_defined?("#{last_part}_ddt") 380 | end 381 | 382 | def self.generate_for(*klasses_or_instances, &block) 383 | all_results = {} 384 | klasses = [] 385 | mock_dict = {} 386 | lazy_triggers = [] 387 | klasses_or_instances.each do |klass_or_instance| 388 | if klass_or_instance.is_a?(String) || klass_or_instance.is_a?(Regexp) 389 | lazy_triggers << Pretentious::LazyTrigger.new(klass_or_instance, stubs: klass_or_instance._get_stub_classes) 390 | else 391 | klass = klass_or_instance.class == Class ? klass_or_instance : klass_or_instance.class 392 | klasses << replace_class(klass) 393 | 394 | mock_klasses = [] 395 | 396 | klass._get_stub_classes.each do |mock_klass| 397 | mock_klasses << replace_class(mock_klass, true) 398 | end unless klass._get_stub_classes.nil? 399 | 400 | mock_dict[klass] = mock_klasses 401 | end 402 | end 403 | 404 | if !watched? 405 | watch_new_instances 406 | block.call 407 | unwatch_new_instances 408 | else 409 | block.call 410 | end 411 | 412 | # check for lazy triggers, collect and then clean 413 | klasses += Pretentious::LazyTrigger.collect_targets.map(&:to_a) 414 | lazy_triggers.each(&:disable!) 415 | klasses.each do |module_space, klass, last_part, new_standin_klass| 416 | # restore the previous class 417 | restore_class module_space, klass, last_part 418 | 419 | mock_dict[klass].each do |mock_module_space, mock_klass, mock_last_part, mock_new_standin_klass| 420 | restore_class mock_module_space, mock_klass, mock_last_part 421 | end if mock_dict[klass] 422 | 423 | generator = test_generator.new 424 | generator.begin_spec(klass) 425 | 426 | generator.body(new_standin_klass._instances) unless new_standin_klass._instances.nil? 427 | 428 | generator.end_spec 429 | 430 | result = all_results[klass] 431 | all_results[klass] = [] if result.nil? 432 | 433 | result_output = generator.output.is_a?(String) ? generator.output.chomp : generator.output 434 | all_results[klass] = { output: result_output, generator: generator.class } 435 | end unless klasses.nil? 436 | 437 | all_results 438 | end 439 | 440 | def self.watch_new_instances 441 | Object.class_eval do 442 | def _get_init_arguments 443 | @_init_arguments 444 | end 445 | 446 | def _set_init_arguments(*args, &block) 447 | @_init_arguments ||= {} 448 | @_init_arguments[:params] = args 449 | unless block.nil? 450 | @_init_arguments[:block] = RecordedProc.new(block) {} 451 | end 452 | @_variable_names = {} 453 | 454 | params = if self.respond_to? :test_class 455 | test_class.instance_method(:initialize).parameters 456 | else 457 | self.class.instance_method(:initialize).parameters 458 | end 459 | @_init_arguments[:params_types] = params 460 | 461 | args.each_with_index do |arg, index| 462 | p = params[index] 463 | @_variable_names[arg.object_id] = p[1].to_s if p && p.size > 1 464 | end unless args.nil? 465 | end 466 | 467 | def _variable_map 468 | (@_variable_names || {}).dup 469 | end 470 | 471 | def _deconstruct 472 | Pretentious::Deconstructor.new.deconstruct([], self) 473 | end 474 | 475 | def _deconstruct_to_ruby(var_name = nil, indentation = 0) 476 | variable_names = {} 477 | 478 | caller_context = binding.of_caller(1) 479 | v_locals = caller_context.eval('local_variables') 480 | 481 | v_locals.each do |v| 482 | variable_value = caller_context.eval("#{v}") 483 | if object_id == variable_value.object_id 484 | variable_names[variable_value.object_id] = v 485 | end 486 | end 487 | 488 | context = Pretentious::Context.new(_variable_map.merge!(variable_names)) 489 | context.register(object_id, var_name) if var_name 490 | 491 | Pretentious::Deconstructor.new.deconstruct_to_ruby(context, indentation, self) 492 | end 493 | end 494 | 495 | # make sure it is set only once 496 | unless Class.instance_methods.include?(:_ddt_old_new) 497 | Class.class_eval do 498 | alias_method :_ddt_old_new, :new 499 | 500 | def new(*args, &block) 501 | lazy_trigger = Pretentious::LazyTrigger.lookup(self.to_s) 502 | instance = nil 503 | if !lazy_trigger.nil? 504 | klass = self 505 | instance = if methods.include? :_current_old_class 506 | _ddt_old_new(*args, &block) 507 | else 508 | module_space, klass, last_part, stand_in_class = Pretentious::Generator.replace_class(self) 509 | lazy_trigger.register_class(module_space, klass, last_part, stand_in_class) 510 | inst = _ddt_old_new(*args, &block) 511 | stand_in_class.new(*([:_no_init, inst] + args), &block) 512 | end 513 | else 514 | instance = _ddt_old_new(*args, &block) 515 | end 516 | 517 | # rescues for handling native objects that don't have standard methods 518 | begin 519 | if instance.respond_to?(:_set_init_arguments) && !instance.respond_to?(:impostor?) && !instance.frozen? 520 | instance._set_init_arguments(*args, &block) 521 | end 522 | rescue NoMethodError 523 | begin 524 | puts "no method error" 525 | instance._set_init_arguments(*args, &block) 526 | rescue NoMethodError 527 | # eat up NoMethodError for now 528 | end 529 | end 530 | 531 | instance 532 | end 533 | end 534 | end 535 | end 536 | 537 | def self.clean_watches 538 | unwatch_new_instances 539 | end 540 | 541 | def self.watched? 542 | Class.respond_to?(:_ddt_old_new) 543 | end 544 | 545 | def self.unwatch_new_instances 546 | if watched? 547 | Class.class_eval do 548 | remove_method :new 549 | alias_method :new, :_ddt_old_new 550 | remove_method :_ddt_old_new 551 | end 552 | end 553 | end 554 | end 555 | end 556 | -------------------------------------------------------------------------------- /lib/pretentious/generator_base.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # base class for spec generators 3 | class GeneratorBase 4 | def initialize(options = {}) 5 | @deconstructor = Pretentious::Deconstructor.new 6 | indentation_count = options[:indentation] || 2 7 | @output_buffer = '' 8 | @_indentation = '' 9 | indentation_count.times do 10 | @_indentation << ' ' 11 | end 12 | end 13 | 14 | def buffer(line, level = 0) 15 | @output_buffer << "#{indentation(level)}#{line}\n" 16 | end 17 | 18 | def buffer_to_string(buffer, line, level = 0) 19 | buffer << "#{indentation(level)}#{line}\n" 20 | end 21 | 22 | def buffer_inline_to_string(buffer, line, level = 0) 23 | buffer << "#{indentation(level)}#{line}" 24 | end 25 | 26 | def buffer_inline(line, level = 0) 27 | @output_buffer << "#{indentation(level)}#{line}" 28 | end 29 | 30 | def setup_fixture(fixture) 31 | variable_map = fixture.let_variables.merge(fixture.object_id => '@fixture') 32 | context = Pretentious::Context.new(variable_map) 33 | declarations, _dependencies = @deconstructor.generate_declarations(context, [], fixture) 34 | 35 | [context, declarations] 36 | end 37 | 38 | def body(instances) 39 | specs = [] 40 | instances.each_with_index do |instance, num| 41 | specs << generate(instance, num + 1) 42 | end 43 | buffer_inline(specs.join("\n")) 44 | end 45 | 46 | protected 47 | 48 | def declare_dependencies(context, args, level) 49 | deconstructor = Pretentious::Deconstructor.new 50 | 51 | args = remove_primitives(args, context.variable_map) 52 | deconstructor.deconstruct_to_ruby(context, level * @_indentation.length, *args) 53 | end 54 | 55 | def remove_primitives(args, let_lookup) 56 | args.select { |a| let_lookup.include?(a.object_id) || !Pretentious::Deconstructor.primitive?(a) } 57 | end 58 | 59 | def params_generator(context, args) 60 | params = [] 61 | args.each do |arg| 62 | if context.variable_map[arg.object_id] 63 | params << context.pick_name(arg.object_id) 64 | else 65 | params << context.value_of(arg) 66 | end 67 | end 68 | params.join(', ') 69 | end 70 | 71 | def desc_params(block) 72 | params = [] 73 | args = block[:params] 74 | names = block[:names] 75 | # puts args.inspect 76 | return '' if args.nil? 77 | 78 | args.each_with_index do |arg, index| 79 | param_name = names[index][1].to_s 80 | arg_value = (arg.is_a? String) ? "#{arg.dump}" : "#{arg}" 81 | if param_name.empty? 82 | params << "#{arg_value}" 83 | else 84 | params << "#{param_name} = #{arg_value}" 85 | end 86 | end 87 | params.join(' ,') 88 | end 89 | 90 | def indentation(level) 91 | buffer = '' 92 | level.times do 93 | buffer << @_indentation 94 | end 95 | buffer 96 | end 97 | 98 | def whitespace(level = 0) 99 | @output_buffer << "#{indentation(level)}\n" 100 | end 101 | 102 | def prettify_method_name(method_name) 103 | if method_name.to_s.end_with? '=' 104 | "#{method_name.to_s.chop} = " 105 | else 106 | method_name.to_s 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/pretentious/lazy_trigger.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | class LazyTrigger 3 | attr_accessor :target_class, :options, :targets 4 | 5 | class Target 6 | attr_accessor :stand_in_klass, :original_klass, :module_space, :name 7 | 8 | def initialize(module_space, klass, last_part, stand_in_class) 9 | @module_space = module_space 10 | @stand_in_klass = stand_in_class 11 | @original_klass = klass 12 | @name = last_part 13 | end 14 | 15 | def to_a 16 | [@module_space, @original_klass, @name, @stand_in_klass] 17 | end 18 | end 19 | 20 | def initialize(target_class, options = {}) 21 | @target_class = target_class 22 | @options = options 23 | @targets = {} 24 | Pretentious::LazyTrigger.register_instance(self) 25 | end 26 | 27 | def disable! 28 | Pretentious::LazyTrigger.unregister_instance(self) 29 | end 30 | 31 | def restore 32 | @targets.each do |_stand_in_klass, target| 33 | Pretentious::Generator.restore_class target.module_space, target.original_klass, target.name 34 | end 35 | end 36 | 37 | def register_class(module_space, klass, last_part, stand_in_class) 38 | target = Pretentious::LazyTrigger::Target.new(module_space, klass, last_part, stand_in_class) 39 | @targets[target.stand_in_klass] = target unless @targets.include? target.stand_in_klass 40 | end 41 | 42 | def match(value) 43 | if @target_class.is_a? Regexp 44 | @target_class.match(value) 45 | elsif @target_class.is_a? String 46 | @target_class == value 47 | else 48 | @target_class.to_s == value 49 | end 50 | end 51 | 52 | class << self 53 | def generate_for_class(generator_class) 54 | all_results = {} 55 | Pretentious::LazyTrigger.collect_targets.each do |target| 56 | standin_klass = target.stand_in_klass 57 | klass = target.original_klass 58 | puts "generate for #{klass}" 59 | generator = generator_class.new 60 | 61 | generator.begin_spec(klass) 62 | generator.body(standin_klass._instances) unless standin_klass._instances.nil? 63 | generator.end_spec 64 | 65 | result = all_results[klass] 66 | all_results[klass] = [] if result.nil? 67 | 68 | result_output = generator.output.is_a?(String) ? generator.output.chomp : generator.output 69 | all_results[klass] = { output: result_output, generator: generator.class } 70 | end 71 | all_results 72 | end 73 | 74 | def lookup(class_name) 75 | @instances ||= [] 76 | @instances.each do |instance| 77 | return instance if instance.match(class_name) 78 | end 79 | nil 80 | end 81 | 82 | def collect_targets 83 | artifacts = [] 84 | @instances.each do |instance| 85 | instance.targets.values.each do |target| 86 | artifacts << target 87 | end 88 | end 89 | artifacts 90 | end 91 | 92 | def collect_artifacts 93 | artifacts = [] 94 | @instances.each do |instance| 95 | instance.targets.values.each do |target| 96 | artifacts << target.stand_in_klass 97 | end 98 | end 99 | artifacts 100 | end 101 | 102 | def clear 103 | @instances = [] 104 | end 105 | 106 | def register_instance(instance) 107 | @instances ||= [] 108 | @instances << instance unless @instances.include? instance 109 | end 110 | 111 | def unregister_instance(instance) 112 | @instances.delete(instance) 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/pretentious/minitest_generator.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # Generator that outputs minitest specs 3 | class MinitestGenerator < Pretentious::GeneratorBase 4 | def self.to_sym 5 | :minitest 6 | end 7 | 8 | def begin_spec(test_class) 9 | @test_class = test_class 10 | buffer('# This file was automatically generated by the pretentious gem') 11 | buffer("require 'minitest_helper'") 12 | buffer("require 'minitest/autorun'") 13 | whitespace 14 | buffer("class #{test_class.name}Test < Minitest::Test") 15 | buffer('end') 16 | whitespace 17 | end 18 | 19 | def end_spec 20 | end 21 | 22 | def output 23 | @output_buffer 24 | end 25 | 26 | private 27 | 28 | def generate(test_instance, instance_count) 29 | output_buffer = '' 30 | if test_instance.is_a? Class 31 | buffer_to_string(output_buffer, "class #{test_instance.test_class.name}Scenario#{instance_count} < #{@test_class.name}Test", 0) 32 | 33 | # class methods 34 | class_method_calls = test_instance.method_calls_by_method 35 | context = Pretentious::Context.new(test_instance.let_variables) 36 | buffer_inline_to_string(output_buffer, generate_specs(context, "#{test_instance.test_class.name}::", 37 | test_instance.test_class.name, class_method_calls)) 38 | 39 | buffer_to_string(output_buffer, 'end', 0) 40 | else 41 | buffer_to_string(output_buffer, "class #{test_instance.test_class.name}Scenario#{instance_count} < #{@test_class.name}Test", 0) 42 | 43 | buffer_to_string(output_buffer, 'def setup', 1) 44 | 45 | context, declarations = setup_fixture(test_instance) 46 | 47 | method_calls = test_instance.method_calls_by_method 48 | specs_buffer = generate_specs(context.subcontext(declarations[:declaration]), "#{test_instance.test_class.name}#", 49 | '@fixture', method_calls) 50 | context.declared_names = {} 51 | buffer_inline_to_string(output_buffer, @deconstructor.build_output(context, 2 * @_indentation.length, declarations)) 52 | buffer_to_string(output_buffer, 'end', 1) 53 | buffer_to_string(output_buffer, '') 54 | 55 | buffer_inline_to_string(output_buffer, specs_buffer) 56 | 57 | buffer_to_string(output_buffer, 'end', 0) 58 | end 59 | output_buffer 60 | end 61 | 62 | def proc_function_generator(block, method) 63 | "func_#{method}(#{Pretentious::Deconstructor.block_params_generator(block)})" 64 | end 65 | 66 | def get_block_source(context, block) 67 | " &#{context.pick_name(block.target_proc.object_id)}" 68 | end 69 | 70 | def generate_expectation(context, fixture, method, params, block, result) 71 | str = '' 72 | block_source = if !block.nil? && block.is_a?(Pretentious::RecordedProc) 73 | get_block_source(context, block) 74 | else 75 | '' 76 | end 77 | 78 | statement = if params.size > 0 79 | "#{fixture}.#{prettify_method_name method}(#{params_generator(context, params)})#{block_source}" 80 | else 81 | stmt = [] 82 | m_stmt = "#{fixture}.#{method}" 83 | m_stmt << "(#{block_source})" unless block_source.empty? 84 | stmt << m_stmt 85 | stmt.join(' ') 86 | end 87 | 88 | if result.is_a? Exception 89 | str << pick_matcher(context, statement, result) 90 | else 91 | str << pick_matcher(context, statement, result) 92 | end 93 | str 94 | end 95 | 96 | def generate_specs(context, context_prefix, fixture, method_calls) 97 | output = '' 98 | buffer_to_string(output, 'def test_current_expectation', 1) 99 | # collect all params 100 | params_collection = [] 101 | mocks_collection = {} 102 | 103 | method_calls.each_key do |k| 104 | info_blocks_arr = method_calls[k] 105 | info_blocks_arr.each do |block| 106 | params_collection |= block[:params] 107 | if !Pretentious::Deconstructor.primitive?(block[:result]) && !block[:result].is_a?(Exception) 108 | params_collection << block[:result] 109 | end 110 | 111 | params_collection << block[:block] unless block[:block].nil? 112 | 113 | block[:context][:calls].each do |mock_block| 114 | k = "#{mock_block[:class]}_#{mock_block[:method]}" 115 | 116 | mocks_collection[k] = [] if mocks_collection[k].nil? 117 | 118 | mocks_collection[k] << mock_block 119 | params_collection << mock_block[:result] 120 | end if block[:context] 121 | end 122 | end if method_calls 123 | 124 | if params_collection.size > 0 125 | deps = declare_dependencies(context, params_collection, 2) 126 | buffer_to_string(output, deps) if deps.strip != '' 127 | end 128 | 129 | if mocks_collection.keys.size > 0 130 | minitest_stub = generate_minitest_stub(context, mocks_collection, 2) do |indentation| 131 | generate_test_scenarios(context, fixture, method_calls, context_prefix, indentation) 132 | end 133 | buffer_to_string(output, minitest_stub, 0) 134 | else 135 | buffer_inline_to_string(output, generate_test_scenarios(context, fixture, method_calls, context_prefix, 2), 0) 136 | end 137 | 138 | buffer_to_string(output, 'end', 1) 139 | output 140 | end 141 | 142 | def generate_test_scenarios(context, fixture, method_calls, context_prefix, indentation_level) 143 | expectations = [] 144 | indentation = '' 145 | indentation_level.times { indentation << @_indentation } 146 | method_calls.each_key do |k| 147 | info_blocks_arr = method_calls[k] 148 | 149 | info_blocks_arr.each do |block| 150 | str = '' 151 | params_desc_str = if block[:params].size > 0 152 | "when passed #{desc_params(block)}" 153 | else 154 | '' 155 | end 156 | 157 | str << "#{indentation}# #{context_prefix}#{k} #{params_desc_str} should return #{context.value_of(block[:result])}\n" 158 | str << "#{indentation}#{generate_expectation(context, fixture, k, block[:params], block[:block], block[:result])}\n" 159 | expectations << str unless expectations.include? str 160 | end 161 | end if method_calls 162 | expectations.join("\n") 163 | end 164 | 165 | def generate_minitest_stub(context, mocks_collection, indentation_level, &block) 166 | str = '' 167 | current_indentation = indentation_level 168 | 169 | mocks_collection.each do |_k, values| 170 | indentation = '' 171 | current_indentation.times { indentation << @_indentation } 172 | vals = values.collect { |v| context.value_of(v[:result]) } 173 | str << "#{indentation}#{values[0][:class]}.stub_any_instance(:#{values[0][:method]}, #{vals[0]}) do\n" 174 | current_indentation += 1 175 | end 176 | 177 | str << block.call(current_indentation) 178 | 179 | current_indentation -= 1 180 | 181 | mocks_collection.each do 182 | indentation = '' 183 | current_indentation.times { indentation << @_indentation } 184 | str << "#{indentation}end\n" 185 | current_indentation -= 1 186 | end 187 | str 188 | end 189 | 190 | def pick_matcher(context, statement, result) 191 | if result.is_a? TrueClass 192 | "assert #{statement}" 193 | elsif result.is_a? FalseClass 194 | "refute #{statement}" 195 | elsif result.nil? 196 | "assert_nil #{statement}" 197 | elsif result.is_a? Exception 198 | "assert_raises(#{result.class}) { #{statement} }" 199 | elsif context.variable_map[result.object_id] 200 | "assert_equal #{context.value_of(result)}, #{statement}" 201 | else 202 | "assert_equal #{Pretentious.value_ize(Pretentious::Context.new, result)}, #{statement}" 203 | end 204 | end 205 | 206 | def self.location(output_folder) 207 | output_folder.nil? ? 'test' : File.join(output_folder, 'test') 208 | end 209 | 210 | def self.naming(output_folder, klass) 211 | klass_name_parts = klass.name.split('::') 212 | last_part = klass_name_parts.pop 213 | File.join(output_folder, "test_#{Pretentious::DdtUtils.to_underscore(last_part)}.rb") 214 | end 215 | 216 | def self.helper(output_folder) 217 | filename = File.join(output_folder, 'minitest_helper.rb') 218 | unless File.exist?(filename) 219 | File.open(filename, 'w') do |f| 220 | f.write("# Place your requires here\n") 221 | f.write("require 'minitest/stub_any_instance'\n") 222 | end 223 | puts "#{filename}" 224 | end 225 | end 226 | end 227 | end 228 | -------------------------------------------------------------------------------- /lib/pretentious/recorded_proc.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # Sublass of Proc that records whatever was passed to it and whatever it returns 3 | class RecordedProc < Proc 4 | attr_reader :target_proc, :return_value 5 | 6 | def initialize(target_proc, is_given_block = false) 7 | @target_proc = target_proc 8 | @return_value = [] 9 | @args = [] 10 | @given_block = is_given_block 11 | @called = false 12 | end 13 | 14 | def given_block? 15 | @given_block 16 | end 17 | 18 | def is_called? 19 | @called 20 | end 21 | 22 | def call(*args, &block) 23 | @called = true 24 | @args << args 25 | return_value = @target_proc.call(*args, &block) 26 | 27 | @return_value << return_value unless @return_value.include? return_value 28 | return_value 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/pretentious/rspec_generator.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # Generator for RSPEC 3 | class RspecGenerator < Pretentious::GeneratorBase 4 | def self.to_sym 5 | :spec 6 | end 7 | 8 | def begin_spec(test_class) 9 | buffer('# This file was automatically generated by the pretentious gem') 10 | buffer("require 'spec_helper'") 11 | whitespace 12 | buffer("RSpec.describe #{test_class.name} do") 13 | end 14 | 15 | def end_spec 16 | buffer('end') 17 | end 18 | 19 | def output 20 | @output_buffer 21 | end 22 | 23 | private 24 | 25 | def generate(test_instance, instance_count) 26 | output_buffer = '' 27 | if test_instance.is_a? Class 28 | context = Pretentious::Context.new(test_instance.let_variables) 29 | # class methods 30 | class_method_calls = test_instance.method_calls_by_method 31 | buffer_inline_to_string(output_buffer, generate_specs(0, context, "#{test_instance.test_class.name}::", test_instance.test_class.name, 32 | class_method_calls)) 33 | else 34 | buffer_to_string(output_buffer, "context 'Scenario #{instance_count}' do", 1) 35 | 36 | buffer_to_string(output_buffer, 'before do', 2) 37 | 38 | context, declarations = setup_fixture(test_instance) 39 | method_calls = test_instance.method_calls_by_method 40 | spec_context = context.subcontext(declarations[:declaration]) 41 | specs_buffer = generate_specs(1, spec_context, "#{test_instance.test_class.name}#", "@fixture", method_calls) 42 | context.declared_names = {} 43 | deconstruct_output = @deconstructor.build_output(context, 3 * @_indentation.length, declarations) 44 | 45 | buffer_inline_to_string(output_buffer, deconstruct_output) 46 | buffer_to_string(output_buffer, 'end', 2) 47 | buffer_to_string(output_buffer, '') 48 | buffer_inline_to_string(output_buffer, specs_buffer) 49 | buffer_to_string(output_buffer, 'end', 1) 50 | end 51 | output_buffer 52 | end 53 | 54 | def proc_function_generator(block, method) 55 | "func_#{method}(#{Pretentious::Deconstructor.block_params_generator(block)})" 56 | end 57 | 58 | def get_block_source(context, block) 59 | " &#{context.pick_name(block.target_proc.object_id)}" 60 | end 61 | 62 | def generate_expectation(indentation_level, context, fixture, method, params, block, result) 63 | output = '' 64 | block_source = if !block.nil? && block.is_a?(Pretentious::RecordedProc) 65 | get_block_source(context, block) 66 | else 67 | '' 68 | end 69 | 70 | statement = if params.size > 0 71 | "#{fixture}.#{prettify_method_name(method)}(#{params_generator(context, params)})#{block_source}" 72 | else 73 | stmt = [] 74 | stmt << "#{fixture}.#{method}" 75 | stmt << "#{block_source}" unless block_source.empty? 76 | stmt.join(' ') 77 | end 78 | 79 | if result.is_a? Exception 80 | buffer_to_string(output, "expect { #{statement} }.to #{pick_matcher(context, result)}", indentation_level + 2) 81 | else 82 | buffer_to_string(output, "expect(#{statement}).to #{pick_matcher(context, result)}", indentation_level + 2) 83 | end 84 | output 85 | end 86 | 87 | def generate_specs(indentation_level, context, context_prefix, fixture, method_calls) 88 | output = '' 89 | buffer_to_string(output, "it 'should pass current expectations' do", indentation_level + 1) 90 | # collect all params 91 | params_collection = [] 92 | mocks_collection = {} 93 | method_call_collection = [] 94 | 95 | return if method_calls.nil? 96 | 97 | method_calls.each_key do |k| 98 | info_blocks_arr = method_calls[k] 99 | info_blocks_arr.each do |block| 100 | method_call_collection << block 101 | params_collection |= block[:params] 102 | if !Pretentious::Deconstructor.primitive?(block[:result]) && !block[:result].kind_of?(Exception) 103 | params_collection << block[:result] 104 | end 105 | 106 | params_collection << block[:block] unless block[:block].nil? 107 | 108 | next unless block[:context] 109 | block[:context][:calls].each do |mock_block| 110 | k = "#{mock_block[:class]}_#{mock_block[:method]}" 111 | 112 | mocks_collection[k] = [] if mocks_collection[k].nil? 113 | 114 | mocks_collection[k] << mock_block 115 | params_collection << mock_block[:result] 116 | end 117 | end 118 | end 119 | 120 | if params_collection.size > 0 121 | deps = declare_dependencies(context, params_collection, indentation_level + 2) 122 | buffer_inline_to_string(output, deps) if deps != '' 123 | end 124 | 125 | if mocks_collection.keys.size > 0 126 | buffer_to_string(output, generate_rspec_stub(context, mocks_collection, 127 | (indentation_level + 2) * @_indentation.length)) 128 | end 129 | 130 | expectations = [] 131 | method_calls.each_key do |k| 132 | info_blocks_arr = method_calls[k] 133 | 134 | info_blocks_arr.each do |block| 135 | str = '' 136 | params_desc_str = if block[:params].size > 0 137 | "when passed #{desc_params(block)}" 138 | else 139 | '' 140 | end 141 | 142 | buffer_to_string(str, "# #{context_prefix}#{k} #{params_desc_str} should return #{context.value_of(block[:result])}", indentation_level + 2) 143 | buffer_inline_to_string(str, generate_expectation(indentation_level, context, fixture, k, block[:params], block[:block], block[:result])) 144 | expectations << str unless expectations.include? str 145 | end 146 | end 147 | buffer_inline_to_string(output, expectations.join("\n")) 148 | buffer_to_string(output, 'end', indentation_level + 1) 149 | output 150 | end 151 | 152 | def generate_rspec_stub(context, mocks_collection, indentation_level) 153 | indentation = '' 154 | 155 | indentation_level.times { indentation << ' ' } 156 | str = '' 157 | mocks_collection.each do |_k, values| 158 | vals = values.collect { |v| context.value_of(v[:result]) } 159 | 160 | # check if all vals are the same and just use one 161 | vals = [vals[0]] if vals.uniq.size == 1 162 | 163 | str << "#{indentation}allow_any_instance_of(#{values[0][:class]}).to receive(:#{values[0][:method]}).and_return(#{vals.join(', ')})\n" 164 | end 165 | str 166 | end 167 | 168 | def pick_matcher(context, result) 169 | if result.is_a? TrueClass 170 | 'be true' 171 | elsif result.is_a? FalseClass 172 | 'be false' 173 | elsif result.nil? 174 | 'be_nil' 175 | elsif result.is_a? Exception 176 | 'raise_error' 177 | elsif context.map_name result.object_id 178 | "eq(#{context.map_name(result.object_id)})" 179 | else 180 | "eq(#{Pretentious.value_ize(Pretentious::Context.new, result)})" 181 | end 182 | end 183 | 184 | def self.location(output_folder) 185 | output_folder.nil? ? 'spec' : File.join(output_folder, 'spec') 186 | end 187 | 188 | def self.naming(output_folder, klass) 189 | klass_name_parts = klass.name.split('::') 190 | last_part = klass_name_parts.pop 191 | File.join(output_folder, "#{Pretentious::DdtUtils.to_underscore(last_part)}_spec.rb") 192 | end 193 | 194 | def self.helper(output_folder) 195 | filename = File.join(output_folder, 'spec_helper.rb') 196 | unless File.exist?(filename) 197 | File.open(filename, 'w') { |f| f.write('# Place your requires here') } 198 | puts "#{filename}" 199 | end 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /lib/pretentious/trigger.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # The trigger class is used for hooking into an existing method 3 | # in order to record the usage of a target class 4 | class Trigger 5 | # options that ca be passed to a trigger 6 | class Options 7 | attr_accessor :output_folder 8 | end 9 | 10 | def initialize(target_class) 11 | @target_class = target_class 12 | @target_class_methods = [] 13 | @target_methods = [] 14 | end 15 | 16 | def method_called(*target_methods) 17 | @target_methods = target_methods 18 | self 19 | end 20 | 21 | def class_method_called(*target_methods) 22 | @target_class_methods = target_methods 23 | self 24 | end 25 | 26 | def spec_for(*klasses, &results_block) 27 | @generator = Pretentious::RspecGenerator 28 | @spec_classes = klasses 29 | @results_block = results_block 30 | install_trigger 31 | end 32 | 33 | def minitest_for(*klasses, &results_block) 34 | @generator = Pretentious::MinitestGenerator 35 | @spec_classes = klasses 36 | @results_block = results_block 37 | install_trigger 38 | end 39 | 40 | def self.output_file(result, klass, output_folder) 41 | file_writer = FileWriter.new({ output_folder: output_folder }) 42 | file_writer.write klass, result 43 | end 44 | 45 | private 46 | 47 | def attach_generator(generator, target, method, spec_classes, results_block, options) 48 | target.send(:define_method, method.to_sym) do |*args, &block| 49 | result = nil 50 | Pretentious::Generator.test_generator = generator 51 | generator_result = Pretentious::Generator.generate_for(*spec_classes) do 52 | result = send(:"_pretentious_orig_#{method}", *args, &block) 53 | end 54 | 55 | results_block.call(generator_result, options) if results_block 56 | 57 | result 58 | end 59 | end 60 | 61 | def install_trigger 62 | @options = Pretentious::Trigger::Options.new 63 | 64 | default_callback = proc do |result_per_generator, options| 65 | output_files = [] 66 | result_per_generator.each do |klass, result| 67 | output_folder = result[:generator].location(options.output_folder) 68 | filename = Pretentious::Trigger.output_file(result, klass, output_folder) 69 | output_files << filename 70 | end 71 | output_files 72 | end 73 | 74 | @results_block = default_callback unless @results_block 75 | 76 | @target_methods.each do |method| 77 | @target_class.class_exec(@target_class, method) do |klass, m| 78 | unless klass.instance_methods.include? :"_pretentious_orig_#{m}" 79 | alias_method :"_pretentious_orig_#{m}", :"#{m}" 80 | end 81 | end 82 | 83 | attach_generator(@generator, @target_class, method, @spec_classes, @results_block, @options) 84 | end 85 | 86 | @target_class_methods.each do |method| 87 | @target_class.singleton_class.class_exec(@target_class, method) do |klass, m| 88 | unless klass.methods.include? :"_pretentious_orig_#{m}" 89 | alias_method :"_pretentious_orig_#{m}", :"#{m}" 90 | end 91 | end 92 | attach_generator(@generator, @target_class.singleton_class, method, 93 | @spec_classes, @results_block, @options) 94 | end 95 | 96 | @options 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/pretentious/utils/file_writer.rb: -------------------------------------------------------------------------------- 1 | module Pretentious 2 | # Utility function for saving pretentious test artifacts to a file. 3 | class FileWriter 4 | # options for the file writer 5 | def initialize(options = {}) 6 | @spec_output_folder ||= (options[:spec_output_folder] || 'generated') 7 | @output_folder = options[:output_folder] || nil 8 | end 9 | 10 | def write(klass, result) 11 | output_folder = result[:generator].location(@output_folder) 12 | spec_output_folder = File.join(output_folder, @spec_output_folder) 13 | FileUtils.mkdir_p result[:generator].location(@output_folder) 14 | FileUtils.mkdir_p spec_output_folder 15 | result[:generator].helper(output_folder) 16 | filename = result[:generator].naming(spec_output_folder, klass) 17 | File.open(filename, 'w') { |f| f.write(result[:output]) } 18 | filename 19 | end 20 | 21 | def write_results(results) 22 | results.each do |g, result_per_generator| 23 | puts "#{g}:" 24 | result_per_generator.each do |klass, result| 25 | puts write(klass, result) 26 | end if result_per_generator 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pretentious/version.rb: -------------------------------------------------------------------------------- 1 | # Pretentious - version 2 | module Pretentious 3 | VERSION = '0.2.4' 4 | end 5 | -------------------------------------------------------------------------------- /pretentious.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'pretentious/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "pretentious" 8 | spec.version = Pretentious::VERSION 9 | spec.authors = ["Joseph Emmanuel Dayo"] 10 | spec.email = ["joseph.dayo@gmail.com"] 11 | spec.summary = %q{Characterization Tests generator for Ruby - Generate tests from existing/legacy code as well as a way to deal with pretentious TDD/BDD developers} 12 | spec.description = %q{Characterization Tests for Ruby - Do you have a pretentious boss or dev lead that pushes you to embrace tdd but for reasons hate it or them? here is a gem to deal with that.} 13 | spec.homepage = "https://github.com/jedld/pretentious" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | spec.add_dependency "binding_of_caller", "~> 0.7.2" 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_development_dependency "rspec", "~> 3.0" 24 | spec.add_development_dependency "minitest", "~> 5.7" 25 | spec.add_development_dependency "minitest-stub_any_instance", "~> 1.0" 26 | spec.add_development_dependency "awesome_print" 27 | spec.add_development_dependency "rspec_junit_formatter", '0.2.2' 28 | spec.add_development_dependency "simplecov" 29 | end 30 | -------------------------------------------------------------------------------- /pretentious.yml: -------------------------------------------------------------------------------- 1 | # Sample pretentious targets file 2 | # use $ to pass a regex or a ruby code that evals to a string 3 | targets: 4 | - class: Meme 5 | - class: $/^TestClass/ 6 | generators: 7 | - rspec 8 | - minitest 9 | examples: 10 | - sample.rb 11 | -------------------------------------------------------------------------------- /run_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git add . 4 | gem build pretentious.gemspec 5 | gem install pretentious-0.2.2.gem 6 | ruby test/test_generator.rb 7 | -------------------------------------------------------------------------------- /sample.rb: -------------------------------------------------------------------------------- 1 | class Meme 2 | def i_can_has_cheezburger? 3 | "OHAI!" 4 | end 5 | 6 | def will_it_blend? 7 | "YES!" 8 | end 9 | end 10 | 11 | meme = Meme.new 12 | meme.i_can_has_cheezburger? 13 | meme.will_it_blend? 14 | 15 | class TestClassR1 16 | SOME_CONSTANT = "CONST" 17 | 18 | def hello 19 | end 20 | end 21 | 22 | class TestClassR2 23 | def hello_again 24 | "hi" 25 | end 26 | 27 | def pass_message(message) 28 | "#{message} #{TestClassR1::SOME_CONSTANT}" 29 | end 30 | end 31 | 32 | test_class1 = TestClassR1.new 33 | test_class1.hello 34 | 35 | test_class2 = TestClassR2.new 36 | test_class2.hello_again 37 | test_class2.pass_message("a message") 38 | -------------------------------------------------------------------------------- /spec/deconstructor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Pretentious::Deconstructor do 4 | before do 5 | @fixture = Pretentious::Deconstructor.new 6 | end 7 | 8 | describe "#build_tree" do 9 | it "should decompose an object" do 10 | message = 'test' 11 | another_object = Pretentious.watch { 12 | TestClass1.new(message) 13 | } 14 | 15 | # Pretentious::Deconstructor#build_tree when passed target_object = # should return {:class=>TestClass1, :id=>2171343920, :composition=>[{:class=>String, :id=>2171343600, :composition=>"test"}]} 16 | expect(@fixture.build_tree(another_object)).to eq(class: TestClass1, 17 | id: another_object.object_id, 18 | composition: [{ class: String, 19 | id: message.object_id, 20 | composition: "test"}], 21 | params_types: [[:req, :message]]) 22 | 23 | end 24 | 25 | context "Special 'native' objects like File" do 26 | it "Decomposes properly" do 27 | file = nil 28 | filename = nil 29 | another_object = Pretentious.watch { 30 | filename = "example.rb" 31 | file = File.new(filename) 32 | TestClass1.new(file) 33 | } 34 | result = @fixture.build_tree(another_object) 35 | result[:composition][0][:composition][0][:id] = filename.object_id 36 | 37 | expect(result) 38 | .to eq( 39 | class: TestClass1, 40 | id: another_object.object_id, 41 | composition: [ 42 | { class: File, 43 | id: file.object_id, 44 | composition: [ 45 | { 46 | class: String, 47 | id: filename.object_id, 48 | composition: 'example.rb' 49 | }] 50 | }], 51 | params_types: [[:req, :message]]) 52 | end 53 | end 54 | end 55 | 56 | describe "Object#_deconstruct_to_ruby" do 57 | it "generates the ruby code to create an object" do 58 | output = Pretentious.watch do 59 | a = "Some type of string" 60 | a._deconstruct_to_ruby 61 | end 62 | expect(output).to eq("a = 'Some type of string'\n") 63 | end 64 | 65 | it "deconstructs arrays types" do 66 | output = Pretentious.watch do 67 | hash = { message: "hello" } 68 | arr = [1, 2, 3, "hello", hash, ['subarray', 2, :symbol]] 69 | test_class = TestClass1.new(arr) 70 | test_class._deconstruct_to_ruby 71 | end 72 | expect(output).to eq("message = [1, 2, 3, 'hello', { message: 'hello' }, ['subarray', 2, :symbol]]\ntest_class = TestClass1.new(message)\n") 73 | end 74 | 75 | it "deconstructs 'native' types" do 76 | file = nil 77 | filename = nil 78 | another_object = Pretentious.watch do 79 | filename = "example.rb" 80 | file = File.new(filename) 81 | test_class = TestClass1.new(file) 82 | test_class._deconstruct_to_ruby 83 | end 84 | expect(another_object).to eq("message = File.new('example.rb')\ntest_class = TestClass1.new(message)\n") 85 | end 86 | 87 | it "has special handling for unresolvable types" do 88 | # objects outside of the watch scope 89 | a = "Some type of string" 90 | b = TestClass1.new("Hello world") 91 | output = Pretentious.watch do 92 | test_class = TestClass3.new(a, b) 93 | test_class._deconstruct_to_ruby 94 | end 95 | expect(output).to eq("a = TestClass1.new # parameters unresolvable. The watcher needs to be installed before this object is created\ntestclass2 = TestClass1.new(a)\ntest_class = TestClass3.new('Some type of string', testclass2)\n") 96 | end 97 | 98 | it "deconstructs hash types" do 99 | output = Pretentious.watch do 100 | hash = { message: "hello", arr: [1, 2, 3], hash: { message2: "msg" } } 101 | test_class = TestClass1.new(hash) 102 | test_class._deconstruct_to_ruby 103 | end 104 | expect(output).to eq("message = { message: 'hello', arr: [1, 2, 3], hash: { message2: 'msg' } }\ntest_class = TestClass1.new(message)\n") 105 | end 106 | 107 | it "deconstruct multiple objects" do 108 | output = Pretentious.watch do 109 | a = "Some type of string" 110 | b = TestClass1.new("Hello world") 111 | test_class = TestClass3.new(a, b) 112 | test_class._deconstruct_to_ruby 113 | end 114 | expect(output).to eq("testclass2 = TestClass1.new('Hello world')\ntest_class = TestClass3.new('Some type of string', testclass2)\n") 115 | end 116 | end 117 | 118 | describe "#deconstruct" do 119 | it "should build list of variables to declare" do 120 | message = "test" 121 | another_object = Pretentious.watch do 122 | TestClass1.new(message) 123 | end 124 | 125 | decons = @fixture.deconstruct([], another_object) 126 | expect(decons).to eq({declaration: 127 | [{id: message.object_id, 128 | class: String, 129 | value: "test", 130 | :used_by=>:inline}, 131 | {id: another_object.object_id, 132 | class: TestClass1, 133 | params_types: [[:req, :message]], 134 | used_by: [], ref: [{:id=>message.object_id, 135 | :class=>String, value: "test", used_by: :inline}]}], 136 | dependency: {message.object_id=>{:id=>message.object_id, 137 | class: String, value: "test", used_by: :inline}, 138 | another_object.object_id=>{id: another_object.object_id, 139 | :class=>TestClass1, 140 | :params_types=>[[:req, :message]], 141 | :used_by=>[], 142 | :ref=>[{:id=>message.object_id, 143 | :class=>String, :value=>"test", :used_by=>:inline}]}}}) 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/generated/fibonacci_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe Fibonacci do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = Fibonacci.new 8 | end 9 | 10 | it 'should pass current expectations' do 11 | n = 1 12 | n_1 = 2 13 | n_2 = 3 14 | n_3 = 4 15 | n_4 = 5 16 | # Fibonacci#fib when passed n = 1 should return 1 17 | expect(@fixture.fib(n)).to eq(n) 18 | 19 | # Fibonacci#fib when passed n = 2 should return 1 20 | expect(@fixture.fib(n_1)).to eq(n) 21 | 22 | # Fibonacci#fib when passed n = 3 should return 2 23 | expect(@fixture.fib(n_2)).to eq(n_1) 24 | 25 | # Fibonacci#fib when passed n = 4 should return 3 26 | expect(@fixture.fib(n_3)).to eq(n_2) 27 | 28 | # Fibonacci#fib when passed n = 5 should return 5 29 | expect(@fixture.fib(n_4)).to eq(n_4) 30 | end 31 | end 32 | 33 | it 'should pass current expectations' do 34 | # Fibonacci::say_hello should return 'hello' 35 | expect(Fibonacci.say_hello).to eq('hello') 36 | end 37 | end -------------------------------------------------------------------------------- /spec/generated/m_d5_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe Digest::MD5 do 5 | it 'should pass current expectations' do 6 | sample = 'This is the digest' 7 | # Digest::MD5::hexdigest when passed "This is the digest" should return '9f12248dcddeda976611d192efaaf72a' 8 | expect(Digest::MD5.hexdigest(sample)).to eq('9f12248dcddeda976611d192efaaf72a') 9 | end 10 | end -------------------------------------------------------------------------------- /spec/generated/meme_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe Meme do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = Meme.new 8 | end 9 | 10 | it 'should pass current expectations' do 11 | # Meme#i_can_has_cheezburger? should return 'OHAI!' 12 | expect(@fixture.i_can_has_cheezburger?).to eq('OHAI!') 13 | 14 | # Meme#will_it_blend? should return 'YES!' 15 | expect(@fixture.will_it_blend?).to eq('YES!') 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /spec/generated/test_class1_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClass1 do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = TestClass1.new('test') 8 | end 9 | 10 | it 'should pass current expectations' do 11 | # TestClass1#message should return 'test' 12 | expect(@fixture.message).to eq('test') 13 | end 14 | end 15 | 16 | context 'Scenario 2' do 17 | before do 18 | @another_object = TestClass1.new('test') 19 | @a = { hello: 'world', test: @another_object, arr_1: [1, 2, 3, 4, 5, @another_object], sub_hash: { yes: true, obj: @another_object } } 20 | @fixture = TestClass1.new(@a) 21 | end 22 | 23 | it 'should pass current expectations' do 24 | a = proc { |message| 25 | @a 26 | } 27 | 28 | test_class1 = nil 29 | b = proc { 30 | # Variable return values ... can't figure out what goes in here... 31 | } 32 | 33 | # TestClass1#print_message should return nil 34 | expect(@fixture.print_message).to be_nil 35 | 36 | # TestClass1#set_block should return a 37 | expect(@fixture.set_block &a).to eq(a) 38 | 39 | # TestClass1#call_block should return @a 40 | expect(@fixture.call_block &b).to eq(@a) 41 | 42 | # TestClass1#something_is_wrong should return e 43 | expect { @fixture.something_is_wrong }.to raise_error 44 | 45 | # TestClass1#just_returns_true should return true 46 | expect(@fixture.just_returns_true).to be true 47 | end 48 | end 49 | 50 | context 'Scenario 3' do 51 | before do 52 | @fixture = TestClass1.new('Hello') 53 | end 54 | 55 | it 'should pass current expectations' do 56 | another_object = TestClass1.new('test') 57 | # TestClass1#return_self when passed message = # should return another_object 58 | expect(@fixture.return_self(another_object)).to eq(another_object) 59 | end 60 | end 61 | 62 | context 'Scenario 4' do 63 | before do 64 | @another_object = TestClass1.new('test') 65 | @fixture = TestClass1.new(@another_object) 66 | end 67 | 68 | it 'should pass current expectations' do 69 | # TestClass1#message should return @another_object 70 | expect(@fixture.message).to eq(@another_object) 71 | end 72 | end 73 | end -------------------------------------------------------------------------------- /spec/generated/test_class2_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClass2 do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = TestClass2.new('This is message 2', nil) 8 | end 9 | 10 | it 'should pass current expectations' do 11 | # TestClass2#print_message should return nil 12 | expect(@fixture.print_message).to be_nil 13 | end 14 | end 15 | 16 | context 'Scenario 2' do 17 | before do 18 | @fixture = TestClass2.new('This is message 3', nil) 19 | end 20 | 21 | it 'should pass current expectations' do 22 | end 23 | end 24 | 25 | context 'Scenario 3' do 26 | before do 27 | @message3 = 'This is message 3' 28 | t = TestClass2.new(@message3, nil) 29 | @fixture = TestClass2.new(t, @message3) 30 | end 31 | 32 | it 'should pass current expectations' do 33 | # TestClass2#test when passed object = "This is message 3" should return 'This is message 3' 34 | expect(@fixture.test(@message3)).to eq(@message3) 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /spec/generated/test_class3_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClass3 do 5 | context 'Scenario 1' do 6 | before do 7 | another_object = TestClass1.new('test') 8 | b = { hello: 'world', test: another_object, arr_1: [1, 2, 3, 4, 5, another_object], sub_hash: { yes: true, obj: another_object } } 9 | test_class_one = TestClass1.new(b) 10 | test_class_two = TestClass2.new('This is message 2', nil) 11 | @fixture = TestClass3.new(test_class_one, test_class_two) 12 | end 13 | 14 | it 'should pass current expectations' do 15 | # TestClass3#show_messages should return 'awesome!!!' 16 | expect(@fixture.show_messages).to eq('awesome!!!') 17 | end 18 | end 19 | 20 | context 'Scenario 2' do 21 | before do 22 | another_object = TestClass1.new('test') 23 | b = { hello: 'world', test: another_object, arr_1: [1, 2, 3, 4, 5, another_object], sub_hash: { yes: true, obj: another_object } } 24 | test_class_one = TestClass1.new(b) 25 | test_class_two = TestClass2.new('This is message 2', nil) 26 | @fixture = TestClass3.new(test_class_one, test_class_two) 27 | end 28 | 29 | it 'should pass current expectations' do 30 | # TestClass3#show_messages should return 'awesome!!!' 31 | expect(@fixture.show_messages).to eq('awesome!!!') 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /spec/generated/test_class4_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClass4 do 5 | context 'Scenario 1' do 6 | before do 7 | b = proc { 8 | 'test' 9 | } 10 | 11 | @fixture = TestClass4.new(&b) 12 | end 13 | 14 | it 'should pass current expectations' do 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /spec/generated/test_class_for_auto_stub_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClassForAutoStub do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = TestClassForAutoStub.new 8 | end 9 | 10 | it 'should pass current expectations' do 11 | a = ['Hello Glorious world', 'HI THERE!!!!'] 12 | allow_any_instance_of(ClassUsedByTestClass).to receive(:stubbed_method).and_return('Hello Glorious world') 13 | allow_any_instance_of(AnotherClassUsedByTestClass).to receive(:get_message).and_return('HI THERE!!!!') 14 | 15 | # TestClassForAutoStub#method_that_uses_the_class_to_stub should return ["Hello Glorious world", "HI THERE!!!!"] 16 | expect(@fixture.method_that_uses_the_class_to_stub).to eq(a) 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /spec/generated/test_class_for_mocks_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClassForMocks do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = TestClassForMocks.new 8 | end 9 | 10 | it 'should pass current expectations' do 11 | a = [2, 3, 4, 5] 12 | allow_any_instance_of(TestMockSubClass).to receive(:test_method).and_return('a return string') 13 | allow_any_instance_of(TestMockSubClass).to receive(:increment_val).and_return(2, 3, 4, 5) 14 | 15 | # TestClassForMocks#method_with_assign= when passed params2 = "test" should return 'test' 16 | expect(@fixture.method_with_assign = ('test')).to eq('test') 17 | 18 | # TestClassForMocks#method_with_usage should return 'a return string' 19 | expect(@fixture.method_with_usage).to eq('a return string') 20 | 21 | # TestClassForMocks#method_with_usage2 should return [2, 3, 4, 5] 22 | expect(@fixture.method_with_usage2).to eq(a) 23 | 24 | # TestClassForMocks#method_with_usage4 should return 'a return string' 25 | expect(@fixture.method_with_usage4).to eq('a return string') 26 | end 27 | end 28 | 29 | context 'Scenario 2' do 30 | before do 31 | @fixture = TestClassForMocks.new 32 | end 33 | 34 | it 'should pass current expectations' do 35 | a = { val: 1, str: 'hello world', message: 'a message' } 36 | allow_any_instance_of(TestMockSubClass).to receive(:return_hash).and_return(a) 37 | 38 | # TestClassForMocks#method_with_usage3 when passed message = "a message" should return a 39 | expect(@fixture.method_with_usage3('a message')).to eq(a) 40 | end 41 | end 42 | end -------------------------------------------------------------------------------- /spec/generated/test_class_r1_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClassR1 do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = TestClassR1.new 8 | end 9 | 10 | it 'should pass current expectations' do 11 | # TestClassR1#hello should return nil 12 | expect(@fixture.hello).to be_nil 13 | end 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /spec/generated/test_class_r2_spec.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'spec_helper' 3 | 4 | RSpec.describe TestClassR2 do 5 | context 'Scenario 1' do 6 | before do 7 | @fixture = TestClassR2.new 8 | end 9 | 10 | it 'should pass current expectations' do 11 | # TestClassR2#hello_again should return 'hi' 12 | expect(@fixture.hello_again).to eq('hi') 13 | 14 | # TestClassR2#pass_message when passed message = "a message" should return 'a message CONST' 15 | expect(@fixture.pass_message('a message')).to eq('a message CONST') 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /spec/generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class TestSubClass 4 | def test_method 5 | "a return string" 6 | end 7 | end 8 | 9 | class TestClass 10 | def initialize(dep = nil) 11 | @test_class2 = TestSubClass.new 12 | end 13 | 14 | def message(params1) 15 | @params1 = params1 16 | end 17 | 18 | def method_with_assign=(params2) 19 | @params2 = "#{params2}!" 20 | end 21 | 22 | def method_with_usage 23 | @test_class2.test_method 24 | end 25 | end 26 | 27 | class DummyGenerator 28 | def initialize 29 | @data = [] 30 | end 31 | 32 | def begin_spec(test_class) 33 | @data << {begin: test_class} 34 | end 35 | 36 | def body(instances) 37 | instances.each_with_index do |instance, num| 38 | generate(instance, num + 1) 39 | end 40 | end 41 | 42 | def generate(test_instance, instance_count) 43 | @data << {instance: test_instance.class.to_s, 44 | instance_method_calls: test_instance.method_calls, 45 | instance_count: instance_count} 46 | end 47 | 48 | def end_spec 49 | @data << :end 50 | end 51 | 52 | def output 53 | @data 54 | end 55 | end 56 | 57 | class DummyGenerator2 58 | def initialize 59 | @data = [] 60 | end 61 | 62 | def begin_spec(test_class) 63 | @data << { begin: test_class } 64 | end 65 | 66 | def body(instances) 67 | instances.each_with_index do |instance, num| 68 | generate(instance, num + 1) 69 | end 70 | end 71 | 72 | def generate(test_instance, instance_count) 73 | @data << {generate: test_instance.class.to_s, instance_count: instance_count} 74 | 75 | deconstructor = Pretentious::Deconstructor.new 76 | 77 | deconstruct_targets = [test_instance] 78 | test_instance.method_calls.each do |method_calls| 79 | deconstruct_targets += method_calls[:params] 80 | end 81 | 82 | deconstruct = deconstructor.deconstruct(test_instance.method_calls, *deconstruct_targets) 83 | deconstruct[:declaration].each do |d| 84 | @data << d 85 | end 86 | 87 | test_instance.method_calls.each do |method_calls| 88 | @data << method_calls 89 | end 90 | @data << :generate_end 91 | end 92 | 93 | def end_spec 94 | @data << :end 95 | end 96 | 97 | def output 98 | @data 99 | end 100 | end 101 | 102 | RSpec.describe Pretentious::Generator do 103 | context 'Pretentious::Generator#impostor_for' do 104 | before do 105 | @impostor = Pretentious::Generator.impostor_for(Object, TestClass) 106 | end 107 | 108 | it "should be a class" do 109 | expect(@impostor.kind_of? Class).to be 110 | end 111 | 112 | it "subject class should be valid" do 113 | expect(@impostor.test_class).to eq TestClass 114 | end 115 | 116 | it 'show contain the same methods' do 117 | expect(@impostor.instance_methods).to include :message 118 | expect(@impostor.instance_methods).to include :method_with_assign= 119 | end 120 | end 121 | 122 | context "Pretentious::Generator#replace_class" do 123 | around(:each) do |example| 124 | @old_class = TestClass 125 | module_space, klass, last, new_class = Pretentious::Generator.replace_class(TestClass) 126 | @new_class = new_class 127 | example.run 128 | Pretentious::Generator.restore_class(module_space, klass, last) 129 | end 130 | 131 | it "new class should be named like the old class" do 132 | expect(@new_class.to_s).to eq "TestClassImpostor" 133 | expect(TestClass == @new_class).to be 134 | end 135 | 136 | it "a new kind of class should be created" do 137 | expect(@old_class == @new_class).to_not be 138 | end 139 | 140 | it "should set instance variables like the real thing" do 141 | instance = @new_class.new 142 | instance.message("hello") 143 | expect(instance.instance_variable_get(:@params1)).to eq "hello" 144 | instance.method_with_assign="world" 145 | expect(instance.instance_variable_get(:@params2)).to eq "world!" 146 | end 147 | end 148 | 149 | context "Pretentious::Generator#generate_for" do 150 | around(:each) do |example| 151 | Pretentious::Generator.test_generator = DummyGenerator 152 | example.run 153 | Pretentious::Generator.test_generator = nil 154 | end 155 | 156 | it "generates call artifacts for target class" do 157 | call_artifacts = Pretentious::Generator.generate_for(TestClass) do 158 | instance = TestClass.new 159 | instance.message('hello') 160 | end 161 | 162 | expect(call_artifacts).to eq(TestClass => { output: 163 | [{ begin: TestClass }, 164 | { instance: 'TestClassImpostor', 165 | instance_method_calls: 166 | [{ method: :message, 167 | params: ["hello"], 168 | block: nil, 169 | names: [[:req, :params1]], 170 | context: {calls: []}, result: 'hello'}], 171 | instance_count: 1}, :end], generator: DummyGenerator}) 172 | end 173 | 174 | context "auto mocks generator" do 175 | it "generates a stub call structure" do 176 | 177 | call_artifacts = Pretentious::Generator.generate_for(TestClass._stub(TestSubClass)) do 178 | instance = TestClass.new 179 | instance.method_with_usage 180 | end 181 | 182 | expect(call_artifacts).to eq({ TestClass => {output: [{:begin=>TestClass}, 183 | {:instance=>"TestClassImpostor", 184 | :instance_method_calls=>[{:method=>:method_with_usage, 185 | :params=>[], :block=>nil, :names=>[], 186 | :context=>{:calls=>[{:method=>:test_method, :params=>[], 187 | :block=>nil, :names=>[], :result=>"a return string", 188 | :class=>TestSubClass}]}, :result=>"a return string"}], 189 | :instance_count=>1}, :end], generator: DummyGenerator}}) 190 | end 191 | 192 | 193 | end 194 | end 195 | 196 | context "reference optimization" do 197 | 198 | around(:each) do |example| 199 | Pretentious::Generator.test_generator= DummyGenerator2 200 | example.run 201 | Pretentious::Generator.test_generator= nil 202 | end 203 | 204 | it "inlines strings when possible" do 205 | message = nil 206 | object = nil 207 | call_artifacts = Pretentious::Generator.generate_for(TestClass1) do 208 | message = "test" 209 | object = TestClass1.new(message) 210 | object.message 211 | end 212 | 213 | expect(call_artifacts[TestClass1]).to eq({output: [ 214 | {begin: TestClass1}, 215 | { 216 | :generate => "TestClass1Impostor", 217 | :instance_count => 1 218 | }, 219 | { 220 | :id => message.object_id, 221 | :class => String, 222 | :value => "test", 223 | :used_by => :inline 224 | }, 225 | { 226 | :id => object.object_id, 227 | :class => TestClass1, 228 | :params_types => [[:req, :message]], 229 | :used_by => [], 230 | :ref => [ {id: message.object_id, class: String, value: "test", used_by: :inline}] 231 | 232 | }, 233 | { 234 | :method => :message, 235 | :params => [], 236 | :block => nil, 237 | :names => [], 238 | :context => { 239 | :calls => [] 240 | }, 241 | :result => "test" 242 | }, 243 | :generate_end, :end], 244 | generator: DummyGenerator2}) 245 | 246 | end 247 | 248 | end 249 | end 250 | -------------------------------------------------------------------------------- /spec/lazy_trigger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Pretentious::LazyTrigger do 4 | it 'Can predeclare even if the class is not yet defined' do 5 | Pretentious.watch do 6 | lazy_trigger = Pretentious::LazyTrigger.new('TestLazyClass') 7 | 8 | class TestLazyClass 9 | def test_method 10 | end 11 | end 12 | 13 | lazy_class = TestLazyClass.new 14 | lazy_class.test_method 15 | 16 | expect(lazy_trigger.targets.keys).to eq([Object.const_get('TestLazyClass')]) 17 | expect(Pretentious::LazyTrigger.collect_artifacts).to eq([Object.const_get('TestLazyClass')]) 18 | expect(TestLazyClass._instances).to eq([lazy_class]) 19 | lazy_trigger.disable! 20 | end 21 | end 22 | 23 | it "can use strings in spec_for to use the lazy trigger" do 24 | call_artifacts = Pretentious::Generator.generate_for("TestLazyClass2") do 25 | class TestLazyClass2 26 | def test_method 27 | end 28 | end 29 | 30 | lazy_class = TestLazyClass2.new 31 | lazy_class.test_method 32 | end 33 | expect(call_artifacts).to eq({TestLazyClass2=>{:output=>"# This file was automatically generated by the pretentious gem\nrequire 'spec_helper'\n\nRSpec.describe TestLazyClass2 do\n context 'Scenario 1' do\n before do\n @fixture = TestLazyClass2.new\n end\n\n it 'should pass current expectations' do\n # TestLazyClass2#test_method should return nil\n expect(@fixture.test_method).to be_nil\n end\n end\nend", :generator=>Pretentious::RspecGenerator}}) 34 | end 35 | 36 | it "can use regex to perform a match on the class name" do 37 | call_artifacts = Pretentious::Generator.generate_for(/^TestLazyClassR/) do 38 | class TestLazyClassR1 39 | def test_method 40 | end 41 | end 42 | 43 | class TestLazyClassR2 44 | def test_method 45 | end 46 | end 47 | 48 | lazy_class1 = TestLazyClassR1.new 49 | lazy_class1.test_method 50 | 51 | lazy_class2 = TestLazyClassR2.new 52 | lazy_class2.test_method 53 | end 54 | 55 | expect(call_artifacts).to eq({ TestLazyClassR1 => { :output => "# This file was automatically generated by the pretentious gem\nrequire 'spec_helper'\n\nRSpec.describe TestLazyClassR1 do\n context 'Scenario 1' do\n before do\n @fixture = TestLazyClassR1.new\n end\n\n it 'should pass current expectations' do\n # TestLazyClassR1#test_method should return nil\n expect(@fixture.test_method).to be_nil\n end\n end\nend", :generator=>Pretentious::RspecGenerator}, TestLazyClassR2=>{:output=>"# This file was automatically generated by the pretentious gem\nrequire 'spec_helper'\n\nRSpec.describe TestLazyClassR2 do\n context 'Scenario 1' do\n before do\n @fixture = TestLazyClassR2.new\n end\n\n it 'should pass current expectations' do\n # TestLazyClassR2#test_method should return nil\n expect(@fixture.test_method).to be_nil\n end\n end\nend", :generator=>Pretentious::RspecGenerator}}) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/minitest_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Pretentious::Generator do 4 | 5 | context 'Pretentious::MinitestGenerator' do 6 | 7 | before do 8 | @fixture = Pretentious::Generator.new 9 | Pretentious::Generator.test_generator = Pretentious::MinitestGenerator 10 | end 11 | 12 | it "classes should have a stub class section" do 13 | Fibonacci._stub(String) 14 | expect(Fibonacci._get_stub_classes).to eq([String]) 15 | end 16 | 17 | it "tracks object calls" do 18 | result = Pretentious::Generator.generate_for(Fibonacci) do 19 | Fibonacci.say_hello 20 | end 21 | expect(result).to eq(Fibonacci => { output: "# This file was automatically generated by the pretentious gem\nrequire 'minitest_helper'\nrequire 'minitest/autorun'\n\nclass FibonacciTest < Minitest::Test\nend\n\nclass FibonacciScenario1 < FibonacciTest\n def test_current_expectation\n # Fibonacci::say_hello should return 'hello'\n assert_equal 'hello', Fibonacci.say_hello\n end\nend", 22 | generator: Pretentious::MinitestGenerator }) 23 | end 24 | 25 | context "proc handling" do 26 | it 'handles blocks passed to methods' do 27 | result = Pretentious.minitest_for(TestClass1) do 28 | test_class = TestClass1.new('message') 29 | test_class.set_block do 30 | 'a string' 31 | end 32 | end 33 | expect(result[TestClass1][:output]).to be < 'expect(@fixture.set_block &c).to eq(a)' 34 | end 35 | end 36 | 37 | it 'Makes sure expectations are unique' do 38 | result = Pretentious.minitest_for(TestClass1) do 39 | test_class = TestClass1.new("message") 40 | test_class.message 41 | test_class.message 42 | end 43 | puts result[TestClass1][:output] 44 | expect(result[TestClass1][:output] 45 | .scan(/assert_equal\ 'message',\ @fixture\.message/).count) 46 | .to eq(1) 47 | end 48 | 49 | it "handles exceptions" do 50 | result = Pretentious.minitest_for(TestClass1) do 51 | test_class = TestClass1.new('message') 52 | begin 53 | test_class.something_is_wrong 54 | rescue StandardError => e 55 | puts 'something is wrong' 56 | end 57 | end 58 | 59 | expect(result[TestClass1][:output]).to match(/assert_raises\(StandardError\) \{ @fixture.something_is_wrong \}/) 60 | end 61 | 62 | it "handles method missing" do 63 | result = Pretentious.minitest_for(TestClassMethodMissing) do 64 | test_class = TestClassMethodMissing.new 65 | test_class.handled_by_method_missing 66 | end 67 | 68 | expect(result[TestClassMethodMissing][:output]).to match("YOU GOT ME!!!!") 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/prententious_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Pretentious::Generator do 4 | context 'Pretentious::Generator' do 5 | before do 6 | @fixture = Pretentious::Generator.new 7 | Pretentious::Generator.test_generator = Pretentious::RspecGenerator 8 | end 9 | 10 | it "uses instance variables when used both in test and in fixtures" do 11 | call_artifacts = Pretentious::Generator.generate_for(TestClass1) do 12 | message = {test: "message"} 13 | object = TestClass1.new(message) 14 | object.return_self(message) 15 | end 16 | expect(call_artifacts[TestClass1][:output]).to eq("# This file was automatically generated by the pretentious gem\nrequire 'spec_helper'\n\nRSpec.describe TestClass1 do\n context 'Scenario 1' do\n before do\n @message = { test: 'message' }\n @fixture = TestClass1.new(@message)\n end\n\n it 'should pass current expectations' do\n # TestClass1#return_self when passed message = {:test=>\"message\"} should return @message\n expect(@fixture.return_self(@message)).to eq(@message)\n end\n end\nend") 17 | end 18 | 19 | it "classes should have a stub class section" do 20 | Fibonacci._stub(String) 21 | expect(Fibonacci._get_stub_classes).to eq([String]) 22 | end 23 | 24 | it "tracks object calls" do 25 | klass = Fibonacci 26 | 27 | result = Pretentious::Generator.generate_for(Fibonacci) do 28 | expect(klass == Fibonacci).to_not be 29 | Fibonacci.say_hello 30 | end 31 | #should clean up after block 32 | expect(klass == Fibonacci).to be 33 | 34 | #should still work 35 | fib = Fibonacci.new 36 | expect(fib.fib(6)).to eq(8) 37 | 38 | expect(result).to eq( 39 | Fibonacci => { output: "# This file was automatically generated by the pretentious gem\nrequire 'spec_helper'\n\nRSpec.describe Fibonacci do\n it 'should pass current expectations' do\n # Fibonacci::say_hello should return 'hello'\n expect(Fibonacci.say_hello).to eq('hello')\n end\nend", 40 | generator: Pretentious::RspecGenerator }) 41 | end 42 | end 43 | 44 | it 'Makes sure expectations are unique' do 45 | result = Pretentious.spec_for(TestClass1) do 46 | test_class = TestClass1.new("message") 47 | test_class.message 48 | test_class.message 49 | end 50 | expect(result[TestClass1][:output] 51 | .scan(/expect\(@fixture.message\).to eq\('message'\)/).count) 52 | .to eq(1) 53 | end 54 | 55 | it "handles exceptions" do 56 | result = Pretentious.spec_for(TestClass1) do 57 | test_class = TestClass1.new('message') 58 | begin 59 | test_class.something_is_wrong 60 | rescue StandardError => e 61 | puts 'something is wrong' 62 | end 63 | end 64 | 65 | expect(result[TestClass1][:output]).to match('expect { @fixture.something_is_wrong }.to raise_error') 66 | end 67 | 68 | it "handles constants inside classes" do 69 | Pretentious.spec_for(TestClass1) do 70 | expect(TestClass1::SOME_CONSTANT).to eq('Yes this is a constant') 71 | end 72 | end 73 | 74 | it "handles method missing" do 75 | result = Pretentious.spec_for(TestClassMethodMissing) do 76 | test_class = TestClassMethodMissing.new 77 | test_class.handled_by_method_missing 78 | end 79 | 80 | expect(result[TestClassMethodMissing][:output]).to match("YOU GOT ME!!!!") 81 | end 82 | 83 | context "proc handling" do 84 | it 'handles blocks passed to methods' do 85 | result = Pretentious.spec_for(TestClass1) do 86 | test_class = TestClass1.new('message') 87 | test_class.set_block do 88 | 'a string' 89 | end 90 | end 91 | expect(result[TestClass1][:output]).to be < 'expect(@fixture.set_block &c).to eq(a)' 92 | end 93 | end 94 | 95 | context 'unobstrusive generator' do 96 | it 'declare watched classes beforehand and capture when certain methods are invoked' do 97 | # declare intention 98 | Pretentious.on(TestClass5).method_called(:test_method).spec_for(Fibonacci) do |results| 99 | expect(results[Fibonacci][:output]).to eq("# This file was automatically generated by the pretentious gem\nrequire 'spec_helper'\n\nRSpec.describe Fibonacci do\n context 'Scenario 1' do\n before do\n @fixture = Fibonacci.new\n end\n\n it 'should pass current expectations' do\n # Fibonacci#fib when passed n = 5 should return 5\n expect(@fixture.fib(5)).to eq(5)\n end\n end\nend") 100 | end 101 | 102 | expect(Pretentious::Generator).to receive(:generate_for).with(Fibonacci).and_call_original 103 | # execute code 104 | class_that_uses_fib = TestClass5.new 105 | result = class_that_uses_fib.test_method 106 | expect(result).to eq(5) 107 | end 108 | 109 | it "outputs to a file when no block is given" do 110 | Pretentious.on(TestClass5).method_called(:test_method).spec_for(Fibonacci) 111 | 112 | expect(Pretentious::Trigger).to receive(:output_file) 113 | 114 | class_that_uses_fib = TestClass5.new 115 | result = class_that_uses_fib.test_method 116 | expect(result).to eq(5) 117 | end 118 | 119 | it "works on class methods" do 120 | Pretentious.on(TestClass5).class_method_called(:class_test_method).spec_for(Fibonacci) 121 | 122 | expect(Pretentious::Trigger).to receive(:output_file) 123 | expect(Pretentious::Generator).to receive(:generate_for).with(Fibonacci).and_call_original 124 | result = TestClass5.class_test_method 125 | 126 | expect(result).to eq(8) 127 | end 128 | 129 | it "works on multiple methods" do 130 | Pretentious.on(TestClass5).method_called(:test_method, :test_method2).spec_for(Fibonacci) 131 | 132 | expect(Pretentious::Trigger).to receive(:output_file).twice 133 | expect(Pretentious::Generator).to receive(:generate_for).twice.with(Fibonacci).and_call_original 134 | 135 | class_that_uses_fib = TestClass5.new 136 | result1 = class_that_uses_fib.test_method 137 | result2 = class_that_uses_fib.test_method2 138 | 139 | expect(result1).to eq(5) 140 | expect(result2).to eq(34) 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'awesome_print' 3 | require_relative '../test_classes' 4 | SimpleCov.start 5 | require 'pretentious' 6 | -------------------------------------------------------------------------------- /spec/utils/filewriter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Pretentious::FileWriter do 4 | 5 | before do 6 | Pretentious.clear_results 7 | Pretentious.spec_for(TestClass1) do 8 | message = { test: 'message' } 9 | object = TestClass1.new(message) 10 | object.return_self(message) 11 | end 12 | @last_results = Pretentious.last_results 13 | @instance = Pretentious::FileWriter.new({ output: 'generated'}) 14 | end 15 | 16 | it 'writes the class output to a file' do 17 | file = double('file') 18 | allow(Pretentious::RspecGenerator).to receive(:helper) 19 | expect(File).to receive(:open).with('spec/generated/test_class1_spec.rb', 'w').and_yield(file) 20 | expect(file).to receive(:write).with(@last_results[:spec][TestClass1][:output]) 21 | expect(FileUtils).to receive(:mkdir_p).with('spec') 22 | expect(FileUtils).to receive(:mkdir_p).with('spec/generated') 23 | 24 | @instance.write_results(Pretentious.last_results) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/generated/test_fibonacci.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class FibonacciTest < Minitest::Test 6 | end 7 | 8 | class FibonacciScenario1 < FibonacciTest 9 | def setup 10 | @fixture = Fibonacci.new 11 | end 12 | 13 | def test_current_expectation 14 | n = 1 15 | n_1 = 2 16 | n_2 = 3 17 | n_3 = 4 18 | n_4 = 5 19 | 20 | # Fibonacci#fib when passed n = 1 should return 1 21 | assert_equal 1, @fixture.fib(n) 22 | 23 | # Fibonacci#fib when passed n = 2 should return 1 24 | assert_equal 1, @fixture.fib(n_1) 25 | 26 | # Fibonacci#fib when passed n = 3 should return 2 27 | assert_equal 2, @fixture.fib(n_2) 28 | 29 | # Fibonacci#fib when passed n = 4 should return 3 30 | assert_equal 3, @fixture.fib(n_3) 31 | 32 | # Fibonacci#fib when passed n = 5 should return 5 33 | assert_equal 5, @fixture.fib(n_4) 34 | end 35 | end 36 | 37 | class FibonacciScenario2 < FibonacciTest 38 | def test_current_expectation 39 | # Fibonacci::say_hello should return 'hello' 40 | assert_equal 'hello', Fibonacci.say_hello 41 | end 42 | end -------------------------------------------------------------------------------- /test/generated/test_m_d5.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class Digest::MD5Test < Minitest::Test 6 | end 7 | 8 | class Digest::MD5Scenario1 < Digest::MD5Test 9 | def test_current_expectation 10 | sample = 'This is the digest' 11 | 12 | # Digest::MD5::hexdigest when passed "This is the digest" should return '9f12248dcddeda976611d192efaaf72a' 13 | assert_equal '9f12248dcddeda976611d192efaaf72a', Digest::MD5.hexdigest(sample) 14 | end 15 | end -------------------------------------------------------------------------------- /test/generated/test_meme.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class MemeTest < Minitest::Test 6 | end 7 | 8 | class MemeScenario1 < MemeTest 9 | def setup 10 | @fixture = Meme.new 11 | end 12 | 13 | def test_current_expectation 14 | # Meme#i_can_has_cheezburger? should return 'OHAI!' 15 | assert_equal 'OHAI!', @fixture.i_can_has_cheezburger? 16 | 17 | # Meme#will_it_blend? should return 'YES!' 18 | assert_equal 'YES!', @fixture.will_it_blend? 19 | end 20 | end -------------------------------------------------------------------------------- /test/generated/test_test_class1.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClass1Test < Minitest::Test 6 | end 7 | 8 | class TestClass1Scenario1 < TestClass1Test 9 | def setup 10 | @fixture = TestClass1.new('test') 11 | end 12 | 13 | def test_current_expectation 14 | # TestClass1#message should return 'test' 15 | assert_equal 'test', @fixture.message 16 | end 17 | end 18 | 19 | class TestClass1Scenario2 < TestClass1Test 20 | def setup 21 | @another_object = TestClass1.new('test') 22 | @a = { hello: 'world', test: @another_object, arr_1: [1, 2, 3, 4, 5, @another_object], sub_hash: { yes: true, obj: @another_object } } 23 | @fixture = TestClass1.new(@a) 24 | end 25 | 26 | def test_current_expectation 27 | a = proc { |message| 28 | @a 29 | } 30 | 31 | test_class1 = nil 32 | b = proc { 33 | # Variable return values ... can't figure out what goes in here... 34 | } 35 | 36 | 37 | # TestClass1#print_message should return nil 38 | assert_nil @fixture.print_message 39 | 40 | # TestClass1#set_block should return a 41 | assert_equal a, @fixture.set_block( &a) 42 | 43 | # TestClass1#call_block should return @a 44 | assert_equal @a, @fixture.call_block( &b) 45 | 46 | # TestClass1#something_is_wrong should return e 47 | assert_raises(StandardError) { @fixture.something_is_wrong } 48 | 49 | # TestClass1#just_returns_true should return true 50 | assert @fixture.just_returns_true 51 | end 52 | end 53 | 54 | class TestClass1Scenario3 < TestClass1Test 55 | def setup 56 | @fixture = TestClass1.new('Hello') 57 | end 58 | 59 | def test_current_expectation 60 | another_object = TestClass1.new('test') 61 | 62 | # TestClass1#return_self when passed message = # should return another_object 63 | assert_equal another_object, @fixture.return_self(another_object) 64 | end 65 | end 66 | 67 | class TestClass1Scenario4 < TestClass1Test 68 | def setup 69 | @another_object = TestClass1.new('test') 70 | @fixture = TestClass1.new(@another_object) 71 | end 72 | 73 | def test_current_expectation 74 | # TestClass1#message should return @another_object 75 | assert_equal @another_object, @fixture.message 76 | end 77 | end -------------------------------------------------------------------------------- /test/generated/test_test_class2.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClass2Test < Minitest::Test 6 | end 7 | 8 | class TestClass2Scenario1 < TestClass2Test 9 | def setup 10 | @fixture = TestClass2.new('This is message 2', nil) 11 | end 12 | 13 | def test_current_expectation 14 | # TestClass2#print_message should return nil 15 | assert_nil @fixture.print_message 16 | end 17 | end 18 | 19 | class TestClass2Scenario2 < TestClass2Test 20 | def setup 21 | @fixture = TestClass2.new('This is message 3', nil) 22 | end 23 | 24 | def test_current_expectation 25 | end 26 | end 27 | 28 | class TestClass2Scenario3 < TestClass2Test 29 | def setup 30 | @message3 = 'This is message 3' 31 | t = TestClass2.new(@message3, nil) 32 | @fixture = TestClass2.new(t, @message3) 33 | end 34 | 35 | def test_current_expectation 36 | # TestClass2#test when passed object = "This is message 3" should return 'This is message 3' 37 | assert_equal 'This is message 3', @fixture.test(@message3) 38 | end 39 | end -------------------------------------------------------------------------------- /test/generated/test_test_class3.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClass3Test < Minitest::Test 6 | end 7 | 8 | class TestClass3Scenario1 < TestClass3Test 9 | def setup 10 | another_object = TestClass1.new('test') 11 | b = { hello: 'world', test: another_object, arr_1: [1, 2, 3, 4, 5, another_object], sub_hash: { yes: true, obj: another_object } } 12 | test_class_one = TestClass1.new(b) 13 | test_class_two = TestClass2.new('This is message 2', nil) 14 | @fixture = TestClass3.new(test_class_one, test_class_two) 15 | end 16 | 17 | def test_current_expectation 18 | # TestClass3#show_messages should return 'awesome!!!' 19 | assert_equal 'awesome!!!', @fixture.show_messages 20 | end 21 | end 22 | 23 | class TestClass3Scenario2 < TestClass3Test 24 | def setup 25 | another_object = TestClass1.new('test') 26 | b = { hello: 'world', test: another_object, arr_1: [1, 2, 3, 4, 5, another_object], sub_hash: { yes: true, obj: another_object } } 27 | test_class_one = TestClass1.new(b) 28 | test_class_two = TestClass2.new('This is message 2', nil) 29 | @fixture = TestClass3.new(test_class_one, test_class_two) 30 | end 31 | 32 | def test_current_expectation 33 | # TestClass3#show_messages should return 'awesome!!!' 34 | assert_equal 'awesome!!!', @fixture.show_messages 35 | end 36 | end -------------------------------------------------------------------------------- /test/generated/test_test_class4.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClass4Test < Minitest::Test 6 | end 7 | 8 | class TestClass4Scenario1 < TestClass4Test 9 | def setup 10 | b = proc { 11 | 'test' 12 | } 13 | 14 | @fixture = TestClass4.new(&b) 15 | end 16 | 17 | def test_current_expectation 18 | end 19 | end -------------------------------------------------------------------------------- /test/generated/test_test_class_for_mocks.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClassForMocksTest < Minitest::Test 6 | end 7 | 8 | class TestClassForMocksScenario1 < TestClassForMocksTest 9 | def setup 10 | @fixture = TestClassForMocks.new 11 | end 12 | 13 | def test_current_expectation 14 | a = [2, 3, 4, 5] 15 | 16 | TestMockSubClass.stub_any_instance(:test_method, 'a return string') do 17 | TestMockSubClass.stub_any_instance(:increment_val, 2) do 18 | # TestClassForMocks#method_with_assign= when passed params2 = "test" should return 'test' 19 | assert_equal 'test', @fixture.method_with_assign = ('test') 20 | 21 | # TestClassForMocks#method_with_usage should return 'a return string' 22 | assert_equal 'a return string', @fixture.method_with_usage 23 | 24 | # TestClassForMocks#method_with_usage2 should return [2, 3, 4, 5] 25 | assert_equal [2, 3, 4, 5], @fixture.method_with_usage2 26 | 27 | # TestClassForMocks#method_with_usage4 should return 'a return string' 28 | assert_equal 'a return string', @fixture.method_with_usage4 29 | end 30 | end 31 | 32 | end 33 | end 34 | 35 | class TestClassForMocksScenario2 < TestClassForMocksTest 36 | def setup 37 | @fixture = TestClassForMocks.new 38 | end 39 | 40 | def test_current_expectation 41 | a = { val: 1, str: 'hello world', message: 'a message' } 42 | 43 | TestMockSubClass.stub_any_instance(:return_hash, a) do 44 | # TestClassForMocks#method_with_usage3 when passed message = "a message" should return a 45 | assert_equal a, @fixture.method_with_usage3('a message') 46 | end 47 | 48 | end 49 | end -------------------------------------------------------------------------------- /test/generated/test_test_class_r1.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClassR1Test < Minitest::Test 6 | end 7 | 8 | class TestClassR1Scenario1 < TestClassR1Test 9 | def setup 10 | @fixture = TestClassR1.new 11 | end 12 | 13 | def test_current_expectation 14 | # TestClassR1#hello should return nil 15 | assert_nil @fixture.hello 16 | end 17 | end 18 | 19 | class TestClassR1ImpostorScenario2 < TestClassR1Test 20 | def test_current_expectation 21 | end 22 | end -------------------------------------------------------------------------------- /test/generated/test_test_class_r2.rb: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the pretentious gem 2 | require 'minitest_helper' 3 | require 'minitest/autorun' 4 | 5 | class TestClassR2Test < Minitest::Test 6 | end 7 | 8 | class TestClassR2Scenario1 < TestClassR2Test 9 | def setup 10 | @fixture = TestClassR2.new 11 | end 12 | 13 | def test_current_expectation 14 | # TestClassR2#hello_again should return 'hi' 15 | assert_equal 'hi', @fixture.hello_again 16 | 17 | # TestClassR2#pass_message when passed message = "a message" should return 'a message CONST' 18 | assert_equal 'a message CONST', @fixture.pass_message('a message') 19 | end 20 | end -------------------------------------------------------------------------------- /test/minitest_helper.rb: -------------------------------------------------------------------------------- 1 | # Place your requires here 2 | require 'minitest/stub_any_instance' 3 | require_relative '../test_classes' 4 | -------------------------------------------------------------------------------- /test/test_generator.rb: -------------------------------------------------------------------------------- 1 | require 'pretentious' 2 | require 'digest/md5' 3 | require 'json' 4 | 5 | class Fibonacci 6 | 7 | def fib(n) 8 | return 0 if (n == 0) 9 | return 1 if (n == 1) 10 | return 1 if (n == 2) 11 | return fib(n - 1) + fib(n - 2) 12 | end 13 | 14 | def self.say_hello 15 | "hello" 16 | end 17 | 18 | end 19 | 20 | 21 | class TestClass1 22 | 23 | def initialize(message) 24 | @message = message 25 | end 26 | 27 | def set_block(&block) 28 | @block=block 29 | end 30 | 31 | def call_block 32 | @block.call(@message) 33 | end 34 | 35 | def message 36 | @message 37 | end 38 | 39 | def print_message 40 | puts @message 41 | end 42 | 43 | def something_is_wrong 44 | raise StandardError.new 45 | end 46 | end 47 | 48 | 49 | class TestClass2 50 | def initialize(message, message2) 51 | @message = {message: message} 52 | end 53 | 54 | def print_message 55 | puts @message[:message] 56 | end 57 | end 58 | 59 | class TestClass3 60 | 61 | def initialize(testclass1, testclass2) 62 | @class1 = testclass1 63 | @class2 = testclass2 64 | end 65 | 66 | def show_messages 67 | @class1.print_message 68 | @class2.print_message 69 | "awesome!!!" 70 | end 71 | 72 | def swap_hash(j, &block) 73 | h = [] 74 | j.each do |k,v| 75 | h << block.call(v,k) 76 | end 77 | h 78 | end 79 | 80 | def check_proc 81 | @class2.call(1,2,3) 82 | end 83 | 84 | end 85 | 86 | #examples 87 | # 88 | #results_ddt = Pretentious::Generator.generate_for(Fibonacci) do 89 | # 90 | # instance = Fibonacci.new 91 | # 92 | # (1..10).each do |n| 93 | # instance.fib(n) 94 | # end 95 | # 96 | # Fibonacci.say_hello 97 | #end 98 | # 99 | #results_ddt.each_value { |v| puts v} 100 | # 101 | #results_md5 = Pretentious::Generator.generate_for(Digest::MD5) do 102 | # sample = "This is the digest" 103 | # Digest::MD5.hexdigest(sample) 104 | #end 105 | # 106 | #results_md5.each_value { |v| puts v} 107 | 108 | #results_composition = Pretentious::Generator.generate_for(TestClass3, TestClass2, TestClass1) do 109 | # another_object = TestClass1.new("test") 110 | # test_class_one = TestClass1.new({hello: "world", test: another_object, arr_1: [1,2,3,4,5, another_object], 111 | # sub_hash: {yes: true, obj: another_object}}) 112 | # test_class_two = TestClass2.new("This is message 2") 113 | # 114 | # class_to_test = TestClass3.new(test_class_one, test_class_two) 115 | # class_to_test.show_messages 116 | # 117 | # class_to_test = TestClass3.new(test_class_one, test_class_two) 118 | # class_to_test.show_messages 119 | # 120 | # puts another_object._deconstruct_to_ruby 121 | # 122 | # class_to_test.swap_hash({a: 1, b: 2}) do |v, k| 123 | # "#{k}_#{v}" 124 | # end 125 | # 126 | # begin 127 | # another_object.something_is_wrong 128 | # rescue Exception=>e 129 | # end 130 | # 131 | # class_to_test = TestClass3.new(test_class_one, Proc.new { |a,b,c| "hello world!"}) 132 | # class_to_test.check_proc 133 | # 134 | # 135 | # 136 | #end 137 | 138 | results_composition = Pretentious::Generator.generate_for(TestClass1, TestClass2, TestClass3) do 139 | unresolvable = File.new("example.rb") 140 | another_object = TestClass1.new("test") 141 | test_class_one = TestClass1.new({hello: "world", test: another_object, arr_1: [1,2,3,4,5, another_object], 142 | sub_hash: {yes: true, obj: another_object}}) 143 | #test_class_two = TestClass2.new("This is message 2") 144 | # 145 | #class_to_test = TestClass3.new(test_class_one, test_class_two) 146 | #class_to_test.show_messages 147 | # 148 | #class_to_test = TestClass3.new(test_class_one, test_class_two) 149 | #class_to_test.show_messages 150 | 151 | test_class_one.set_block { |message| 152 | message 153 | } 154 | 155 | test_class_one.call_block 156 | test_class_one = TestClass1.new(unresolvable) 157 | test_class_one.print_message 158 | end 159 | 160 | results_composition.each_value { |v| puts v} 161 | -------------------------------------------------------------------------------- /test_classes.rb: -------------------------------------------------------------------------------- 1 | require 'digest/md5' 2 | 3 | class Fibonacci 4 | 5 | def fib(n) 6 | return 0 if (n == 0) 7 | return 1 if (n == 1) 8 | return 1 if (n == 2) 9 | return fib(n - 1) + fib(n - 2) 10 | end 11 | 12 | def self.say_hello 13 | "hello" 14 | end 15 | 16 | end 17 | 18 | class Meme 19 | def i_can_has_cheezburger? 20 | "OHAI!" 21 | end 22 | 23 | def will_it_blend? 24 | "YES!" 25 | end 26 | end 27 | 28 | class TestClassMethodMissing 29 | def method_missing(method, *args, &block) 30 | if (method == :handled_by_method_missing) 31 | return "YOU GOT ME!!!!" 32 | else 33 | super(method, *args, &block) 34 | end 35 | end 36 | end 37 | 38 | class TestClass1 39 | SOME_CONSTANT = 'Yes this is a constant' 40 | 41 | def initialize(message) 42 | @message = message 43 | end 44 | 45 | def set_block(&block) 46 | @block = block 47 | end 48 | 49 | def call_block 50 | @block.call(@message) 51 | end 52 | 53 | def message 54 | @message 55 | end 56 | 57 | def just_returns_true 58 | true 59 | end 60 | 61 | def print_message 62 | puts @message 63 | end 64 | 65 | def invoke_class 66 | @message.print_message 67 | end 68 | 69 | def something_is_wrong 70 | fail StandardError 71 | end 72 | 73 | def return_self(message) 74 | message 75 | end 76 | end 77 | 78 | 79 | class TestClass2 80 | def initialize(message, message2) 81 | @message = {message: message} 82 | end 83 | 84 | def print_message 85 | puts @message[:message] 86 | end 87 | 88 | def test(object) 89 | object 90 | end 91 | end 92 | 93 | class TestClass3 94 | 95 | def initialize(testclass1, testclass2) 96 | @class1 = testclass1 97 | @class2 = testclass2 98 | end 99 | 100 | def show_messages 101 | @class1.print_message 102 | @class2.print_message 103 | "awesome!!!" 104 | end 105 | 106 | def change_message(message) 107 | "#{message}!" 108 | end 109 | 110 | def swap_hash(j, &block) 111 | h = [] 112 | j.each do |k,v| 113 | h << block.call(v,k) 114 | end 115 | h 116 | end 117 | 118 | def check_proc 119 | @class2.call(1,2,3) 120 | end 121 | 122 | end 123 | 124 | class TestClass4 125 | 126 | def initialize(&block) 127 | @message = block.call 128 | end 129 | 130 | def message 131 | @message 132 | end 133 | 134 | def to_s 135 | @message 136 | end 137 | 138 | end 139 | 140 | class TestClass5 141 | 142 | def test_method 143 | fibonacci = Fibonacci.new 144 | fibonacci.fib(5) 145 | end 146 | 147 | def test_method2 148 | fibonacci = Fibonacci.new 149 | fibonacci.fib(9) 150 | end 151 | 152 | def self.class_test_method 153 | fibonacci = Fibonacci.new 154 | fibonacci.fib(6) 155 | end 156 | end 157 | 158 | class TestMockSubClass 159 | 160 | def initialize 161 | @val = 1 162 | end 163 | 164 | def test_method 165 | "a return string" 166 | end 167 | 168 | def increment_val 169 | @val += 1 170 | @val 171 | end 172 | 173 | def return_hash(message) 174 | {val: @val, str: "hello world", message: message} 175 | end 176 | end 177 | 178 | class TestClassForMocks 179 | 180 | def initialize 181 | @test_class2 = TestMockSubClass.new 182 | end 183 | 184 | def message(params1) 185 | @params1 = params1 186 | end 187 | 188 | def method_with_assign=(params2) 189 | @params2 = "#{params2}!" 190 | @params2 191 | end 192 | 193 | def method_with_usage 194 | @test_class2.test_method 195 | end 196 | 197 | def method_with_usage2 198 | results = [] 199 | results << @test_class2.increment_val 200 | results << @test_class2.increment_val 201 | results << @test_class2.increment_val 202 | results << @test_class2.increment_val 203 | results 204 | end 205 | 206 | def method_with_usage4 207 | @test_class2.test_method 208 | @test_class2.test_method 209 | @test_class2.test_method 210 | end 211 | 212 | def method_with_usage3(message) 213 | @test_class2.return_hash(message) 214 | end 215 | 216 | end 217 | 218 | class ClassUsedByTestClass 219 | 220 | def stubbed_method 221 | "Hello Glorious world" 222 | end 223 | end 224 | 225 | class AnotherClassUsedByTestClass 226 | 227 | def get_message 228 | "HI THERE!!!!" 229 | end 230 | end 231 | 232 | class TestClassForAutoStub 233 | 234 | def initialize 235 | @class_to_be_used = ClassUsedByTestClass.new 236 | @class_to_be_used2 = AnotherClassUsedByTestClass.new 237 | end 238 | 239 | def method_that_uses_the_class_to_stub 240 | @class_to_be_used 241 | @class_to_be_used2 242 | return_values = [] 243 | return_values << @class_to_be_used.stubbed_method 244 | return_values << @class_to_be_used2.get_message 245 | return_values 246 | end 247 | end 248 | 249 | class TestClassR1 250 | SOME_CONSTANT = "CONST" 251 | 252 | def hello 253 | end 254 | end 255 | 256 | class TestClassR2 257 | def hello_again 258 | "hi" 259 | end 260 | 261 | def pass_message(message) 262 | "#{message} #{TestClassR1::SOME_CONSTANT}" 263 | end 264 | end 265 | --------------------------------------------------------------------------------