infinity_test 1.0.3 → 2.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +31 -0
  3. data/.gitignore +3 -0
  4. data/.rspec +2 -2
  5. data/AI_INTEGRATION_IDEAS.md +203 -0
  6. data/Gemfile +3 -15
  7. data/HISTORY.md +84 -0
  8. data/History.markdown +82 -0
  9. data/{.infinity_test → INFINITY_TEST} +18 -15
  10. data/LICENSE.txt +2 -2
  11. data/README.md +867 -0
  12. data/Rakefile +1 -65
  13. data/TODO.markdown +38 -24
  14. data/bin/infinity_test +1 -4
  15. data/images/fuuu/pending.png +0 -0
  16. data/images/fuuu/success.png +0 -0
  17. data/infinity_test.gemspec +41 -189
  18. data/lib/infinity_test/core/auto_discover.rb +67 -0
  19. data/lib/infinity_test/core/base.rb +369 -0
  20. data/lib/infinity_test/core/callback.rb +59 -0
  21. data/lib/infinity_test/core/changed_file.rb +13 -0
  22. data/lib/infinity_test/core/command_builder.rb +48 -0
  23. data/lib/infinity_test/core/command_runner.rb +38 -0
  24. data/lib/infinity_test/core/configuration_merge.rb +37 -0
  25. data/lib/infinity_test/core/continuous_test_server.rb +124 -0
  26. data/lib/infinity_test/core/load_configuration.rb +48 -0
  27. data/lib/infinity_test/core/notifier.rb +59 -0
  28. data/lib/infinity_test/core/options.rb +134 -0
  29. data/lib/infinity_test/core/runner.rb +18 -0
  30. data/lib/infinity_test/core/version.rb +5 -0
  31. data/lib/infinity_test/framework/base.rb +93 -0
  32. data/lib/infinity_test/framework/django.rb +109 -0
  33. data/lib/infinity_test/framework/elixir_mix.rb +47 -0
  34. data/lib/infinity_test/framework/fast_api.rb +104 -0
  35. data/lib/infinity_test/framework/padrino.rb +30 -0
  36. data/lib/infinity_test/framework/phoenix.rb +72 -0
  37. data/lib/infinity_test/framework/python_package.rb +97 -0
  38. data/lib/infinity_test/framework/rails.rb +26 -0
  39. data/lib/infinity_test/framework/rocket.rb +70 -0
  40. data/lib/infinity_test/framework/rubygems.rb +29 -0
  41. data/lib/infinity_test/framework/rust_cargo.rb +69 -0
  42. data/lib/infinity_test/framework/shared_example.rb +47 -0
  43. data/lib/infinity_test/observer/base.rb +40 -0
  44. data/lib/infinity_test/observer/filewatcher.rb +72 -0
  45. data/lib/infinity_test/observer/listen.rb +74 -0
  46. data/lib/infinity_test/observer/shared_example.rb +35 -0
  47. data/lib/infinity_test/old_dsl/configuration.rb +26 -0
  48. data/lib/infinity_test/strategy/base.rb +50 -0
  49. data/lib/infinity_test/strategy/elixir_default.rb +20 -0
  50. data/lib/infinity_test/strategy/python_default.rb +22 -0
  51. data/lib/infinity_test/strategy/rbenv.rb +34 -0
  52. data/lib/infinity_test/strategy/ruby_default.rb +21 -0
  53. data/lib/infinity_test/strategy/rust_default.rb +21 -0
  54. data/lib/infinity_test/strategy/rvm.rb +52 -0
  55. data/lib/infinity_test/strategy/shared_example.rb +32 -0
  56. data/lib/infinity_test/test_framework/base.rb +64 -0
  57. data/lib/infinity_test/test_framework/cargo_test.rb +49 -0
  58. data/lib/infinity_test/test_framework/ex_unit.rb +53 -0
  59. data/lib/infinity_test/test_framework/pytest.rb +65 -0
  60. data/lib/infinity_test/test_framework/rspec.rb +48 -0
  61. data/lib/infinity_test/test_framework/shared_example.rb +56 -0
  62. data/lib/infinity_test/test_framework/test_unit.rb +57 -0
  63. data/lib/infinity_test.rb +66 -35
  64. data/spec/infinity_test/core/auto_discover_spec.rb +175 -0
  65. data/spec/infinity_test/core/base_spec.rb +240 -0
  66. data/spec/infinity_test/core/callback_spec.rb +89 -0
  67. data/spec/infinity_test/core/changed_file_spec.rb +26 -0
  68. data/spec/infinity_test/core/command_builder_spec.rb +38 -0
  69. data/spec/infinity_test/core/configuration_merge_spec.rb +124 -0
  70. data/spec/infinity_test/core/continuous_test_server_spec.rb +116 -0
  71. data/spec/infinity_test/core/load_configuration_spec.rb +43 -0
  72. data/spec/infinity_test/core/notifier_spec.rb +151 -0
  73. data/spec/infinity_test/core/options_spec.rb +168 -0
  74. data/spec/infinity_test/core/runner_spec.rb +17 -0
  75. data/spec/infinity_test/framework/base_spec.rb +55 -0
  76. data/spec/infinity_test/framework/django_spec.rb +95 -0
  77. data/spec/infinity_test/framework/elixir_mix_spec.rb +44 -0
  78. data/spec/infinity_test/framework/fast_api_spec.rb +96 -0
  79. data/spec/infinity_test/framework/padrino_spec.rb +58 -0
  80. data/spec/infinity_test/framework/phoenix_spec.rb +85 -0
  81. data/spec/infinity_test/framework/python_package_spec.rb +95 -0
  82. data/spec/infinity_test/framework/rails_spec.rb +58 -0
  83. data/spec/infinity_test/framework/rocket_spec.rb +69 -0
  84. data/spec/infinity_test/framework/rubygems_spec.rb +34 -0
  85. data/spec/infinity_test/framework/rust_cargo_spec.rb +94 -0
  86. data/spec/infinity_test/observer/base_spec.rb +78 -0
  87. data/spec/infinity_test/observer/filewatcher_spec.rb +51 -0
  88. data/spec/infinity_test/observer/listen_spec.rb +50 -0
  89. data/spec/infinity_test/{builder_spec.rb → strategy/base_spec.rb} +1 -2
  90. data/spec/infinity_test/strategy/elixir_default_spec.rb +46 -0
  91. data/spec/infinity_test/strategy/python_default_spec.rb +56 -0
  92. data/spec/infinity_test/strategy/rbenv_spec.rb +70 -0
  93. data/spec/infinity_test/strategy/ruby_default_spec.rb +49 -0
  94. data/spec/infinity_test/strategy/rust_default_spec.rb +56 -0
  95. data/spec/infinity_test/strategy/rvm_spec.rb +86 -0
  96. data/spec/infinity_test/test_framework/cargo_test_spec.rb +145 -0
  97. data/spec/infinity_test/test_framework/ex_unit_spec.rb +153 -0
  98. data/spec/infinity_test/test_framework/pytest_spec.rb +182 -0
  99. data/spec/infinity_test/test_framework/rspec_spec.rb +119 -0
  100. data/spec/infinity_test/test_framework/test_unit_spec.rb +193 -0
  101. data/spec/spec_helper.rb +34 -119
  102. metadata +315 -177
  103. data/.rvmrc +0 -1
  104. data/Gemfile.lock +0 -62
  105. data/Readme.markdown +0 -147
  106. data/Tasks +0 -4
  107. data/VERSION.yml +0 -5
  108. data/buzz_images/buzz_lightyear.jpg +0 -0
  109. data/buzz_images/buzz_lightyear_continencia.gif +0 -0
  110. data/buzz_images/to_infinity_and_beyond.png +0 -0
  111. data/features/heuristics.feature +0 -23
  112. data/features/support/env.rb +0 -2
  113. data/images/fuuu/sucess.png +0 -0
  114. data/lib/infinity_test/application.rb +0 -362
  115. data/lib/infinity_test/application_library/rails.rb +0 -94
  116. data/lib/infinity_test/application_library/rubygems.rb +0 -43
  117. data/lib/infinity_test/binary_path.rb +0 -43
  118. data/lib/infinity_test/builder.rb +0 -66
  119. data/lib/infinity_test/command.rb +0 -58
  120. data/lib/infinity_test/configuration.rb +0 -277
  121. data/lib/infinity_test/continuous_testing.rb +0 -40
  122. data/lib/infinity_test/dependencies.rb +0 -80
  123. data/lib/infinity_test/environment.rb +0 -15
  124. data/lib/infinity_test/heuristics.rb +0 -36
  125. data/lib/infinity_test/heuristics_helper.rb +0 -9
  126. data/lib/infinity_test/options.rb +0 -96
  127. data/lib/infinity_test/runner.rb +0 -38
  128. data/lib/infinity_test/test_framework.rb +0 -110
  129. data/lib/infinity_test/test_library/bacon.rb +0 -55
  130. data/lib/infinity_test/test_library/cucumber.rb +0 -22
  131. data/lib/infinity_test/test_library/rspec.rb +0 -60
  132. data/lib/infinity_test/test_library/test_unit.rb +0 -52
  133. data/lib/infinity_test/test_unit_loader.rb +0 -5
  134. data/spec/factories/buzz/lib/buzz.rb +0 -0
  135. data/spec/factories/buzz/spec/buzz_spec.rb +0 -0
  136. data/spec/factories/company/Gemfile +0 -0
  137. data/spec/factories/company/lib/company.rb +0 -0
  138. data/spec/factories/company/test/company_test.rb +0 -0
  139. data/spec/factories/images/failure.png +0 -0
  140. data/spec/factories/images/pending.png +0 -0
  141. data/spec/factories/images/sucess.png +0 -0
  142. data/spec/factories/infinity_test +0 -5
  143. data/spec/factories/infinity_test_example +0 -7
  144. data/spec/factories/slinky/spec/slinky/slinky_spec.rb +0 -0
  145. data/spec/factories/travel/lib/travel.rb +0 -0
  146. data/spec/factories/travel/test/partner_test.rb +0 -0
  147. data/spec/factories/travel/test/travel_test.rb +0 -0
  148. data/spec/factories/wood/lib/wood.rb +0 -0
  149. data/spec/factories/wood/spec/wood_spec.rb +0 -0
  150. data/spec/infinity_test/application_library/rails_spec.rb +0 -140
  151. data/spec/infinity_test/application_library/rubygems_spec.rb +0 -52
  152. data/spec/infinity_test/application_spec.rb +0 -434
  153. data/spec/infinity_test/binary_path_spec.rb +0 -72
  154. data/spec/infinity_test/command_spec.rb +0 -53
  155. data/spec/infinity_test/configuration_spec.rb +0 -382
  156. data/spec/infinity_test/continuous_testing_spec.rb +0 -25
  157. data/spec/infinity_test/environment_spec.rb +0 -23
  158. data/spec/infinity_test/heuristics_helper_spec.rb +0 -15
  159. data/spec/infinity_test/heuristics_spec.rb +0 -127
  160. data/spec/infinity_test/options_spec.rb +0 -111
  161. data/spec/infinity_test/runner_spec.rb +0 -42
  162. data/spec/infinity_test/test_framework_spec.rb +0 -127
  163. data/spec/infinity_test/test_library/bacon_spec.rb +0 -150
  164. data/spec/infinity_test/test_library/cucumber_spec.rb +0 -8
  165. data/spec/infinity_test/test_library/rspec_spec.rb +0 -189
  166. data/spec/infinity_test/test_library/test_unit_spec.rb +0 -184
  167. data/spec/infinity_test_spec.rb +0 -40
  168. /data/images/faces/{sucess.png → success.png} +0 -0
  169. /data/images/hands/{sucess.png → success.png} +0 -0
  170. /data/images/mario_bros/{sucess.jpg → success.jpg} +0 -0
  171. /data/images/rails/{sucess.png → success.png} +0 -0
  172. /data/images/rubies/{sucess.png → success.png} +0 -0
  173. /data/images/simpson/{sucess.jpg → success.jpg} +0 -0
  174. /data/images/street_fighter/{sucess.jpg → success.jpg} +0 -0
  175. /data/images/toy_story/{sucess.png → success.png} +0 -0
@@ -0,0 +1,48 @@
1
+ module InfinityTest
2
+ module Core
3
+ class LoadConfiguration
4
+ attr_accessor :global_file, :project_file
5
+
6
+ def initialize
7
+ @global_file = File.expand_path('~/INFINITY_TEST')
8
+ @project_file = './INFINITY_TEST'
9
+ @old_configuration = InfinityTest::OldDSL::Configuration
10
+ end
11
+
12
+ # Load the Configuration file
13
+ #
14
+ # Command line options can be persisted in an INFINITY_TEST file in a project.
15
+ # You can also store an INFINITY_TEST file in your home directory (~/INFINITY_TEST)
16
+ #
17
+ # Precedence is:
18
+ # command line
19
+ # ./INFINITY_TEST
20
+ # ~/INFINITY_TEST
21
+ #
22
+ # Example:
23
+ #
24
+ # ~/INFINITY_TEST -> infinity_test { notifications :osascript }
25
+ #
26
+ # ./INFINITY_TEST -> infinity_test { notifications :terminal_notifier } # High Priority
27
+ #
28
+ # After the load the Notifications Framework will be Terminal Notifier
29
+ #
30
+ def load!
31
+ load_global_file!
32
+ load_project_file!
33
+ end
34
+
35
+ def load_global_file!
36
+ load_file(@global_file)
37
+ end
38
+
39
+ def load_project_file!
40
+ load_file(@project_file)
41
+ end
42
+
43
+ def load_file(file)
44
+ load(file) if File.exist?(file)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,59 @@
1
+ module InfinityTest
2
+ module Core
3
+ class Notifier
4
+ include ::Notifiers
5
+
6
+ attr_reader :test_framework, :library
7
+
8
+ IMAGES = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'images'))
9
+
10
+ delegate :test_message, to: :test_framework
11
+
12
+ def initialize(options)
13
+ @test_framework = options.fetch(:test_framework)
14
+ @library = options.fetch(:library)
15
+ end
16
+
17
+ def notify
18
+ send(library).title(RUBY_VERSION).message(test_message).image(image).notify
19
+ end
20
+
21
+ def image
22
+ return success_image if test_framework.success?
23
+
24
+ if test_framework.failure?
25
+ failure_image
26
+ else
27
+ pending_image
28
+ end
29
+ end
30
+
31
+ def success_image
32
+ Core::Base.success_image || find_image(:success)
33
+ end
34
+
35
+ def failure_image
36
+ Core::Base.failure_image || find_image(:failure)
37
+ end
38
+
39
+ def pending_image
40
+ Core::Base.pending_image || find_image(:pending)
41
+ end
42
+
43
+ def find_image(image_type)
44
+ Dir.glob(File.join(images_dir, "#{image_type.to_s}*")).first
45
+ end
46
+
47
+ def images_dir
48
+ mode = Core::Base.mode
49
+
50
+ case mode
51
+ when Symbol
52
+ File.join(IMAGES, mode.to_s)
53
+ when String
54
+ File.expand_path(File.join(mode))
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,134 @@
1
+ module InfinityTest
2
+ module Core
3
+ class Options
4
+ attr_accessor :arguments, :options_parser, :strategy, :bundler, :verbose
5
+ attr_accessor :rubies, :specific_options, :test_framework, :framework, :infinity_and_beyond
6
+ attr_accessor :notifications, :mode, :just_watch, :focus
7
+
8
+ def initialize(*arguments)
9
+ @arguments = arguments.flatten.clone
10
+ @options_parser = new_options_parser
11
+ end
12
+
13
+ def new_options_parser
14
+ OptionParser.new do |option|
15
+ %w(
16
+ ruby_strategy
17
+ ruby_versions
18
+ options_to_added_in_the_command
19
+ test_framework_to_be_run
20
+ app_framework
21
+ notifications_library
22
+ image_mode
23
+ infinity_and_beyond_option
24
+ just_watch_option
25
+ focus_option
26
+ verbose_mode
27
+ skip_bundler
28
+ ).each do |option_to_parse|
29
+ send(option_to_parse, option)
30
+ end
31
+ banner(option)
32
+ help(option)
33
+ end
34
+ end
35
+
36
+ def parse!
37
+ @options_parser.parse!(@arguments)
38
+
39
+ self
40
+ end
41
+
42
+ def ruby_strategy(option)
43
+ option.on('--ruby strategy', 'Ruby Manager. Ex.: auto_discover, rvm, rbenv, ruby_default') do |strategy|
44
+ @strategy = strategy.to_sym
45
+ end
46
+ end
47
+
48
+ def ruby_versions(option)
49
+ option.on('--rubies=rubies', 'Specify Ruby version(s) to test against') do |versions|
50
+ @rubies = versions.split(',')
51
+ end
52
+ end
53
+
54
+ def options_to_added_in_the_command(option)
55
+ option.on('--options=options', 'Pass the options to be added. Ex. -Ilib-Itest-Iapp') do |options|
56
+ @specific_options = options.split('-').join(' -').strip
57
+ end
58
+ end
59
+
60
+ def test_framework_to_be_run(option)
61
+ option.on('--test library', 'Test Framework to be run. Ex.: auto_discover, rspec, test_unit.') do |library|
62
+ @test_framework = library.to_sym
63
+ end
64
+ end
65
+
66
+ def app_framework(option)
67
+ option.on('--framework library', 'Application Framework to be run and added the patterns to search changed files. Ex.: auto_discover, rails, rubygems, padrino.') do |library|
68
+ @framework = library.to_sym
69
+ end
70
+ end
71
+
72
+ def notifications_library(option)
73
+ option.on('--notifications library', 'Notification library to use. Ex.: auto_discover, osascript, terminal_notifier, notify_send, dunstify.') do |library|
74
+ @notifications = library.to_sym
75
+ end
76
+ end
77
+
78
+ def image_mode(option)
79
+ option.on('--mode theme', 'Image theme for notifications. Ex.: simpson, faces, fuuu, hands, mario_bros, rails, rubies, street_fighter, toy_story.') do |theme|
80
+ @mode = theme.to_sym
81
+ end
82
+ end
83
+
84
+ def infinity_and_beyond_option(option)
85
+ option.on('-n', '--no-infinity-and-beyond', 'Run tests and exit. Useful in a Continuous Integration environment.') do
86
+ @infinity_and_beyond = false
87
+ end
88
+ end
89
+
90
+ def just_watch_option(option)
91
+ option.on('-j', '--just-watch', 'Skip initial test run and only watch for file changes. Useful for large applications.') do
92
+ @just_watch = true
93
+ end
94
+ end
95
+
96
+ def focus_option(option)
97
+ option.on('-f', '--focus [FILE]', 'Focus on specific tests. Use "failures" for failed tests, or provide a file path.') do |file|
98
+ @focus = file == 'failures' ? :failures : file
99
+ end
100
+ end
101
+
102
+ def verbose_mode(option)
103
+ option.on('--no-verbose', "Don't print commands before executing them") do
104
+ @verbose = false
105
+ end
106
+ end
107
+
108
+ def skip_bundler(option)
109
+ option.on('--no-bundler', "Bypass Infinity Test's Bundler support") do
110
+ @bundler = false
111
+ end
112
+ end
113
+
114
+ def banner(option)
115
+ option.banner = [ "Usage: infinity_test [options]", "Run tests"].join("\n")
116
+ end
117
+
118
+ def help(option)
119
+ option.on_tail("--help", "You're looking at it.") do
120
+ print option.help
121
+ exit
122
+ end
123
+ end
124
+
125
+ def verbose?
126
+ @verbose
127
+ end
128
+
129
+ def bundler?
130
+ @bundler
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,18 @@
1
+ module InfinityTest
2
+ module Core
3
+ class Runner
4
+ attr_accessor :options
5
+
6
+ def initialize(*arguments)
7
+ @options = Options.new(arguments).parse!
8
+ end
9
+
10
+ def start
11
+ Core::LoadConfiguration.new.load!
12
+ Core::Base.merge!(options)
13
+ Core::AutoDiscover.new(Core::Base).discover_libraries
14
+ Core::Base.start_continuous_test_server
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module InfinityTest
2
+ module Core
3
+ VERSION = '2.0.0.rc2'
4
+ end
5
+ end
@@ -0,0 +1,93 @@
1
+ module InfinityTest
2
+ module Framework
3
+ class Base
4
+ attr_reader :continuous_test_server, :hike
5
+ delegate :observer, :test_framework, :extension, to: :continuous_test_server
6
+ delegate :watch, :watch_dir, to: :observer
7
+
8
+ def initialize(continuous_test_server)
9
+ @continuous_test_server = continuous_test_server
10
+ @hike = Hike::Trail.new(Dir.pwd)
11
+ end
12
+
13
+ def heuristics!
14
+ hike.append_extension(extension)
15
+ hike.append_path(test_framework.test_dir)
16
+ heuristics
17
+ end
18
+
19
+ # This method is called for the InfinityTest before starting the observer
20
+ #
21
+ def heuristics
22
+ raise NotImplementedError, "not implemented in #{self}"
23
+ end
24
+
25
+ # Put all the requires to autodiscover use your framework instead of others.
26
+ #
27
+ def self.run?
28
+ raise NotImplementedError, "not implemented in #{self}"
29
+ end
30
+
31
+ # Run all the strategy again.
32
+ #
33
+ def run_all
34
+ continuous_test_server.run_strategy
35
+ end
36
+
37
+ # Run the same changed file.
38
+ # E.g: the user saves the test file, runs the test file.
39
+ #
40
+ # @param changed_file [<InfinityTest::Core::ChangedFile>]
41
+ #
42
+ def run_file(changed_file)
43
+ continuous_test_server.rerun_strategy(changed_file.name)
44
+ end
45
+
46
+ # Run test based on the changed file.
47
+ #
48
+ # @param changed_file [<InfinityTest::Core::ChangedFile>]
49
+ #
50
+ def run_test(changed_file)
51
+ file_name = "#{changed_file.path}_#{test_framework.test_dir}.#{extension}"
52
+ file = hike.find(file_name)
53
+ continuous_test_server.rerun_strategy(file) if file.present?
54
+ end
55
+
56
+ # Auto-discover and watch all directories under app/ that contain Ruby files.
57
+ # Maps app/components to spec/components, app/wizards to spec/wizards, etc.
58
+ # Skips directories without .rb files (e.g., app/views, app/assets).
59
+ #
60
+ def watch_app_dirs
61
+ return unless File.directory?('app')
62
+
63
+ Dir.glob('app/*').each do |app_dir|
64
+ next unless File.directory?(app_dir)
65
+ next unless Dir.glob("#{app_dir}/**/*.rb").any?
66
+
67
+ watch_dir(app_dir) { |file| run_test_in_subdir(file, app_dir) }
68
+ end
69
+ end
70
+
71
+ # Run test preserving the subdirectory structure.
72
+ # E.g: app/components/wizard.rb -> spec/components/wizard_spec.rb
73
+ #
74
+ # @param changed_file [<InfinityTest::Core::ChangedFile>]
75
+ # @param app_dir [String] The app directory (e.g., 'app/components')
76
+ #
77
+ def run_test_in_subdir(changed_file, app_dir)
78
+ subdir = File.basename(app_dir)
79
+ test_file = File.join(test_framework.test_dir, subdir, "#{changed_file.path}_#{test_framework.test_dir}.#{extension}")
80
+
81
+ if File.exist?(test_file)
82
+ continuous_test_server.rerun_strategy(test_file)
83
+ else
84
+ # Fallback: try finding with hike in test_dir/subdir
85
+ hike.prepend_path(File.join(test_framework.test_dir, subdir))
86
+ file_name = "#{changed_file.path}_#{test_framework.test_dir}.#{extension}"
87
+ file = hike.find(file_name)
88
+ continuous_test_server.rerun_strategy(file) if file.present?
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,109 @@
1
+ module InfinityTest
2
+ module Framework
3
+ class Django < Base
4
+ delegate :test_dir, :test_helper_file, to: :test_framework
5
+
6
+ # Override to use .py extension for Python test files.
7
+ #
8
+ def heuristics!
9
+ hike.append_extension('.py')
10
+ hike.append_path(test_framework.test_dir)
11
+ heuristics
12
+ end
13
+
14
+ # Add Heuristics to the observer run on pattern changes!
15
+ #
16
+ # ==== Heuristics
17
+ # * Watch all Django app directories
18
+ # * Watch test dir (.py files) and run the changed file
19
+ # * Watch conftest.py and run all tests
20
+ #
21
+ def heuristics
22
+ watch_django_apps
23
+ watch_dir(test_dir, :py) { |file| run_file(file) } if File.directory?(test_dir.to_s)
24
+ watch(test_helper_file) { run_all } if File.exist?(test_helper_file.to_s)
25
+ end
26
+
27
+ # Auto-discover and watch Django app directories.
28
+ # Django apps have models.py, views.py, or apps.py
29
+ #
30
+ def watch_django_apps
31
+ django_apps = detect_django_apps
32
+
33
+ django_apps.each do |app_dir|
34
+ next unless File.directory?(app_dir)
35
+
36
+ watch_dir(app_dir, :py) { |file| run_app_test(file, app_dir) }
37
+ end
38
+ end
39
+
40
+ # Run test for a Django app file.
41
+ # Maps myapp/models.py -> myapp/tests/test_models.py or myapp/tests.py
42
+ #
43
+ # @param changed_file [<InfinityTest::Core::ChangedFile>]
44
+ # @param app_dir [String] The Django app directory
45
+ #
46
+ def run_app_test(changed_file, app_dir)
47
+ basename = File.basename(changed_file.path, '.py')
48
+ app_name = File.basename(app_dir)
49
+
50
+ # Try app/tests/test_*.py pattern
51
+ test_file = File.join(app_dir, 'tests', "test_#{basename}.py")
52
+ if File.exist?(test_file)
53
+ continuous_test_server.rerun_strategy(test_file)
54
+ return
55
+ end
56
+
57
+ # Try app/tests.py (single test file)
58
+ test_file = File.join(app_dir, 'tests.py')
59
+ if File.exist?(test_file)
60
+ continuous_test_server.rerun_strategy(test_file)
61
+ return
62
+ end
63
+
64
+ # Fallback: run all app tests
65
+ test_dir = File.join(app_dir, 'tests')
66
+ if File.directory?(test_dir)
67
+ continuous_test_server.rerun_strategy(test_dir)
68
+ end
69
+ end
70
+
71
+ # ==== Returns
72
+ # TrueClass: Find a Django project (manage.py exists)
73
+ # FalseClass: Not a Django project
74
+ #
75
+ def self.run?
76
+ File.exist?('manage.py') && django_project?
77
+ end
78
+
79
+ def self.django_project?
80
+ return false unless File.exist?('manage.py')
81
+ content = File.read('manage.py')
82
+ content.include?('django') || content.include?('DJANGO')
83
+ rescue
84
+ false
85
+ end
86
+
87
+ private
88
+
89
+ def detect_django_apps
90
+ apps = []
91
+
92
+ # Find directories with Django app markers
93
+ Dir.glob('*').each do |dir|
94
+ next unless File.directory?(dir)
95
+ next if %w[tests test static templates media venv .venv node_modules].include?(dir)
96
+
97
+ # Check for Django app markers
98
+ has_models = File.exist?(File.join(dir, 'models.py'))
99
+ has_views = File.exist?(File.join(dir, 'views.py'))
100
+ has_apps = File.exist?(File.join(dir, 'apps.py'))
101
+
102
+ apps << dir if has_models || has_views || has_apps
103
+ end
104
+
105
+ apps
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,47 @@
1
+ module InfinityTest
2
+ module Framework
3
+ class ElixirMix < Base
4
+ delegate :test_dir, :test_helper_file, to: :test_framework
5
+
6
+ # Override to use .exs extension for Elixir test files.
7
+ #
8
+ def heuristics!
9
+ hike.append_extension('.exs')
10
+ hike.append_path(test_framework.test_dir)
11
+ heuristics
12
+ end
13
+
14
+ # Add Heuristics to the observer run on pattern changes!
15
+ #
16
+ # ==== Heuristics
17
+ # * Observe lib dir (.ex files) and run corresponding test.
18
+ # * Observe the test dir (.exs files) and run the same changed file.
19
+ # * Observe the test helper file and run all tests.
20
+ #
21
+ def heuristics
22
+ watch_dir(:lib, :ex) { |file| run_test(file) }
23
+ watch_dir(test_dir, :exs) { |file| run_file(file) }
24
+ watch(test_helper_file) { run_all }
25
+ end
26
+
27
+ # Run test based on the changed file.
28
+ # Maps lib/my_app/user.ex -> test/my_app/user_test.exs
29
+ #
30
+ # @param changed_file [<InfinityTest::Core::ChangedFile>]
31
+ #
32
+ def run_test(changed_file)
33
+ file_name = "#{changed_file.path}_test.exs"
34
+ file = hike.find(file_name)
35
+ continuous_test_server.rerun_strategy(file) if file.present?
36
+ end
37
+
38
+ # ==== Returns
39
+ # TrueClass: Find a mix.exs in the user current dir
40
+ # FalseClass: Don't Find a mix.exs in the user current dir
41
+ #
42
+ def self.run?
43
+ File.exist?('mix.exs')
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,104 @@
1
+ module InfinityTest
2
+ module Framework
3
+ class FastApi < Base
4
+ delegate :test_dir, :test_helper_file, to: :test_framework
5
+
6
+ # Override to use .py extension for Python test files.
7
+ #
8
+ def heuristics!
9
+ hike.append_extension('.py')
10
+ hike.append_path(test_framework.test_dir)
11
+ heuristics
12
+ end
13
+
14
+ # Add Heuristics to the observer run on pattern changes!
15
+ #
16
+ # ==== Heuristics
17
+ # * Watch app dir (.py files) for FastAPI applications
18
+ # * Watch src dir (.py files) if exists
19
+ # * Watch routers/endpoints directories
20
+ # * Watch test dir (.py files) and run the changed file
21
+ # * Watch conftest.py and run all tests
22
+ #
23
+ def heuristics
24
+ watch_fastapi_dirs
25
+ watch_dir(test_dir, :py) { |file| run_file(file) }
26
+ watch(test_helper_file) { run_all } if File.exist?(test_helper_file.to_s)
27
+ end
28
+
29
+ # Auto-discover and watch FastAPI source directories.
30
+ # Common patterns: app/, src/, routers/, api/
31
+ #
32
+ def watch_fastapi_dirs
33
+ fastapi_dirs = detect_fastapi_source_dirs
34
+
35
+ fastapi_dirs.each do |dir|
36
+ next unless File.directory?(dir)
37
+ next unless Dir.glob("#{dir}/**/*.py").any?
38
+
39
+ watch_dir(dir, :py) { |file| run_test(file) }
40
+ end
41
+ end
42
+
43
+ # Run test based on the changed file.
44
+ #
45
+ # @param changed_file [<InfinityTest::Core::ChangedFile>]
46
+ #
47
+ def run_test(changed_file)
48
+ test_file = find_test_file(changed_file.path)
49
+ continuous_test_server.rerun_strategy(test_file) if test_file.present?
50
+ end
51
+
52
+ # ==== Returns
53
+ # TrueClass: Find a FastAPI project (app/main.py or main.py with FastAPI)
54
+ # FalseClass: Not a FastAPI project
55
+ #
56
+ def self.run?
57
+ return true if File.exist?('app/main.py') && fastapi_in_file?('app/main.py')
58
+ return true if File.exist?('main.py') && fastapi_in_file?('main.py')
59
+ return true if File.exist?('src/main.py') && fastapi_in_file?('src/main.py')
60
+ false
61
+ end
62
+
63
+ def self.fastapi_in_file?(file)
64
+ return false unless File.exist?(file)
65
+ content = File.read(file)
66
+ content.include?('fastapi') || content.include?('FastAPI')
67
+ rescue
68
+ false
69
+ end
70
+
71
+ private
72
+
73
+ def detect_fastapi_source_dirs
74
+ dirs = []
75
+ dirs << 'app' if File.directory?('app')
76
+ dirs << 'src' if File.directory?('src')
77
+ dirs << 'routers' if File.directory?('routers')
78
+ dirs << 'api' if File.directory?('api')
79
+ dirs << 'endpoints' if File.directory?('endpoints')
80
+
81
+ # Also watch root .py files for simple FastAPI apps
82
+ dirs << '.' if Dir.glob('*.py').any? { |f| !f.start_with?('test_') }
83
+
84
+ dirs.uniq
85
+ end
86
+
87
+ def find_test_file(source_path)
88
+ basename = File.basename(source_path, '.py')
89
+
90
+ patterns = [
91
+ "test_#{basename}.py",
92
+ "#{basename}_test.py"
93
+ ]
94
+
95
+ patterns.each do |pattern|
96
+ file = hike.find(pattern)
97
+ return file if file.present?
98
+ end
99
+
100
+ nil
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,30 @@
1
+ module InfinityTest
2
+ module Framework
3
+ class Padrino < Base
4
+ delegate :test_dir, :test_helper_file, to: :test_framework
5
+
6
+ # Add Heuristics to the observer run on pattern changes!
7
+ #
8
+ # ==== Heuristics
9
+ # * Watch all app/* directories and run corresponding tests/specs
10
+ # * Watch lib dir and run corresponding tests
11
+ # * Watch test/spec dir and run the changed file
12
+ # * Watch test/spec helper and run all
13
+ #
14
+ def heuristics
15
+ watch_app_dirs
16
+ watch_dir(:lib) { |file| run_test(file) }
17
+ watch_dir(test_dir) { |file| run_file(file) }
18
+ watch(test_helper_file) { run_all }
19
+ end
20
+
21
+ # ==== Returns
22
+ # TrueClass: Find the config/apps.rb in the user current dir
23
+ # FalseClass: Don't Find the config/apps.rb in the user current dir
24
+ #
25
+ def self.run?
26
+ File.exist?(File.expand_path('./config/apps.rb'))
27
+ end
28
+ end
29
+ end
30
+ end