Class: FutoSpec

Inherits:
Object
  • Object
show all
Includes:
RSpec::Matchers
Defined in:
lib/futo-spec.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ FutoSpec

Returns a new instance of FutoSpec.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/futo-spec.rb', line 95

def initialize(opts={})
  Whirly.configure spinner: "dots"
  Whirly.configure append_newline: false
  Whirly.configure color: false

  @cases = Array.new
  @chizu_files = Array.new
  @chizus = Array.new
  @unmatched = Set.new
  @included_ins = Array.new

  if $debug
    dpa '-- debug mode --'
    puts
  end

  if opts.include? :dry_run
    $dry_run = true
    pa 'dry run', COLORS[:init], :bright
  end

  if opts.include? :specified_file
    dpa "specified file: #{$specified_file}"
  end

  if opts.include? :headless
    $headless = true
    dpa 'headless mode'
  end

  if opts.include? :markdown
    markdown_only
  else
    look_for_envrb_and_parse
    test_case_lines = process_specified_file
    create_test_cases(test_case_lines)
    find_matching_chizu_files
    process_chizu_files
    match_cases_to_chizus
  end
end

Instance Attribute Details

#casesObject

Returns the value of attribute cases.



93
94
95
# File 'lib/futo-spec.rb', line 93

def cases
  @cases
end

#chizuObject

Returns the value of attribute chizu.



93
94
95
# File 'lib/futo-spec.rb', line 93

def chizu
  @chizu
end

#included_insObject

Returns the value of attribute included_ins.



93
94
95
# File 'lib/futo-spec.rb', line 93

def included_ins
  @included_ins
end

#unmatchedObject

Returns the value of attribute unmatched.



93
94
95
# File 'lib/futo-spec.rb', line 93

def unmatched
  @unmatched
end

Instance Method Details

#add_additional_lines_context_to_specified_line(all_lines, idx) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/futo-spec.rb', line 208

def add_additional_lines_context_to_specified_line(all_lines, idx)
  starting_slice = all_lines.slice(idx, all_lines.length)
  final_idx = nil
  starting_slice.each_with_index do |ll, ii|
    if is_newline? ll
      final_idx = ii
      break
    end
    if final_idx == nil
      # no newline found through the rest of the futo
      final_idx = starting_slice.length
    end
  end
  final_slice = starting_slice.slice(0, final_idx)
  return final_slice
end

#add_case_to_specObject



225
226
227
228
229
230
231
# File 'lib/futo-spec.rb', line 225

def add_case_to_spec
  # don't add the last test case
  unless @new_case_label == ''
    dpa "adding new test case: #{@new_case_label}"
    @cases << FutoCase.new(@new_case_label, @new_case_bullets)
  end
end

#add_new_chizu(kkey, commands) ⇒ Object



365
366
367
# File 'lib/futo-spec.rb', line 365

def add_new_chizu(kkey, commands)
  @chizus << ChizuEntry.new(kkey, commands)
end

#begin_new_caseObject



233
234
235
236
# File 'lib/futo-spec.rb', line 233

def begin_new_case
  @new_case_label = ''
  @new_case_bullets = Array.new
end

#breakpoint_with_local_vars(local_vars) ⇒ Object



488
489
490
491
492
493
# File 'lib/futo-spec.rb', line 488

def breakpoint_with_local_vars(local_vars)
  bind = binding
  bind.local_variable_set(:ish, '555')
  eval('breakpoint; puts "ishly: #{ish}"', bind).binding
  puts
end

#create_test_cases(test_case_lines) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/futo-spec.rb', line 292

def create_test_cases(test_case_lines)
  begin_new_case

  test_case_lines.each do |line|
    l0 = line.gsub('(DONE)','').gsub('(done)','')
    ll = l0.lstrip.rstrip
    if is_newline? ll
      dpa "found newline: #{ll}, saving existing case and starting a new one"
      add_case_to_spec
      begin_new_case
    else
      if is_mock_data? ll
        dpa "found mock data: #{ll}", COLORS[:setup]
        load_mock_data(ll)
      elsif is_bullet? ll
        dpa "found bullet: #{ll}", COLORS[:setup]
        new_bullet(ll)
      elsif is_asterisk? ll
        dpa "found asterisk, treating as description: #{ll}", COLORS[:setup]
        label = ll.gsub('*', '').lstrip
        new_label(label)
      elsif is_description? ll
        dpa "found new description: #{ll}", COLORS[:setup]
        new_label(ll)
      else
        raise RuntimeError, "could not find matching chizu entry type for: #{ll}"
      end
    end
    dpa "test cases loaded: #{@cases.length}"
  end
  # catch anything left over
  add_case_to_spec
end

#discover_and_process_spec_filesObject



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/futo-spec.rb', line 159

def discover_and_process_spec_files
  dpa "no file specified, discovering all .futo or .spec files ...", COLORS[:init], :bright
  futo_files = []
  test_case_lines = []

  Find.find("#{Dir.pwd}/futo") do |ff|
    if ff.end_with? '.futo' or ff.end_with? 'spec'
      fn = ff.split('/').last
      futo_files << fn
    end
  end

  futo_files.each { |fn| test_case_lines += process_specific_file(fn) }
  return test_case_lines
end

#exec_casesObject



526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/futo-spec.rb', line 526

def exec_cases
  puts; puts
  @cases.each do |test_case|
    title = "case: #{test_case.description}"
    pa "\u22EF" * ( title.length + 5 ), COLORS[:exec]
    pa "  case: #{test_case.description}", COLORS[:exec], :bright
    puts
    test_case.bullet_points.each do |bullet|
      pa "\u229A #{bullet}", COLORS[:exec]
      run_commands_in_block_context(bullet)
    end
  end
  puts; puts; puts
end

#find_matching_chizu_filesObject



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/futo-spec.rb', line 340

def find_matching_chizu_files
  without_suffix = $specified_file.chomp('.futo')
  base = "#{Dir.pwd}/futo/chizus"
  path = "#{base}/#{without_suffix}.chizu"
  unless File.exist? path
    pa "#{$specified_file}: couldn't find matching .chizu file", COLORS[:warning]
  else
    dpa "loading matching #{$specified_file}.chizu ...", COLORS[:setup]
    @chizu_files << path
  end

  search_dir = "#{base}/shared"
  dpa "loading shared chizus ...", COLORS[:setup]
  Find.find(search_dir) do |ff|
    if ff.end_with? 'chizu'
      dpa "loading #{ff}"
      @chizu_files << ff
    end
  end
end

#init_test(line) ⇒ Object



281
282
283
284
285
286
287
288
289
290
# File 'lib/futo-spec.rb', line 281

def init_test(line)
  prefix = line.split(':').last.lstrip
  fn = "./initialize/#{prefix}.initialize.rb"
  if File.exist?(fn)
    load(fn)
  else
    pa "failed to find setup file #{fn} for line: #{line}", COLORS[:red]
  end
  puts
end

#is_asterisk?(line) ⇒ Boolean

Returns:

  • (Boolean)


256
257
258
# File 'lib/futo-spec.rb', line 256

def is_asterisk?(line)
  return line.start_with?('*')
end

#is_bullet?(line) ⇒ Boolean

Returns:

  • (Boolean)


260
261
262
# File 'lib/futo-spec.rb', line 260

def is_bullet?(line)
  return line.start_with?('-') || line.start_with?('>')
end

#is_description?(line) ⇒ Boolean

Returns:

  • (Boolean)


248
249
250
251
252
253
254
# File 'lib/futo-spec.rb', line 248

def is_description?(line)
  return false if is_bullet? line
  return false if is_newline? line
  return false if is_mock_data? line
  return false if is_asterisk? line
  return true
end

#is_included_in?(chizu) ⇒ Boolean

Returns:

  • (Boolean)


429
430
431
# File 'lib/futo-spec.rb', line 429

def is_included_in?(chizu)
  return chizu.associated_commands.first.include? 'included_in'
end

#is_mock_data?(line) ⇒ Boolean

Returns:

  • (Boolean)


268
269
270
# File 'lib/futo-spec.rb', line 268

def is_mock_data?(line)
  return line.start_with?('** mock data:')
end

#is_newline?(line) ⇒ Boolean

Returns:

  • (Boolean)


264
265
266
# File 'lib/futo-spec.rb', line 264

def is_newline?(line)
  return line == ''
end

#is_todo?(chizu) ⇒ Boolean

Returns:

  • (Boolean)


425
426
427
# File 'lib/futo-spec.rb', line 425

def is_todo?(chizu)
  return chizu.associated_commands.include?('TODO')
end

#load_chizu_commands(ff) ⇒ Object



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/futo-spec.rb', line 369

def load_chizu_commands(ff)
  dpa "loading chizu commands from file: #{ff}"
  File.open(ff) do |file|
    lines = file.readlines(chomp:true)
    kkey = ''
    associated_commands = Array.new

    processing_stanza = false
    inside_begin_end_block = false
    begin_end_block_string = ''

    lines.each do |ll|
      if processing_stanza
        if inside_begin_end_block
          puts "processing begin-end command: #{ll}"
          if ll.lstrip.start_with? 'end'
            begin_end_block_string += " #{ll.lstrip};"
            associated_commands << begin_end_block_string
            begin_end_block_string = ''
            inside_begin_end_block = false
          else
            begin_end_block_string += " #{ll.lstrip};"
          end
        else
          if ll.strip.start_with? 'begin'
            inside_begin_end_block= true
            begin_end_block_string += "#{ll.lstrip};"
          elsif ll.start_with? 'end'
            processing_stanza = false
            add_new_chizu(kkey, associated_commands)
            kkey = ''
            associated_commands = Array.new
          else
            associated_commands << ll.lstrip
          end
        end
      else
        #puts "processing description line: #{ll}"
        if ll.start_with? 'On'
          processing_stanza = true
          using_single_quotes = single_quoted_line?(ll)
          if using_single_quotes
            kkey = ll.split("'")[1]
          else
            kkey = ll.split('"')[1]
          end
        elsif ll == "\n" || ll == ''
          next
        else
          pa "encountered unexpected line: #{ll}", :red, :bright
        end
      end
    end
  end
end

#load_mock_data(ll) ⇒ Object



272
273
274
275
276
277
278
279
# File 'lib/futo-spec.rb', line 272

def load_mock_data(ll)
  # ll is the full line including '** mock data:'
  fn = ll.split(' ').last.gsub("'",'').gsub('"','')
  # now we have the filename minus futo/
  path = "futo/#{fn}"
  md = File.readlines(path, chomp:true)
  @mock_data = md
end

#look_for_envrb_and_parseObject



148
149
150
151
152
153
154
155
156
157
# File 'lib/futo-spec.rb', line 148

def look_for_envrb_and_parse
  if Dir.children(Dir.pwd).include? 'futo'
    if Dir.children("#{Dir.pwd}/futo").include? '_glue'
      if Dir.children("#{Dir.pwd}/futo/_glue").include? 'env.rb'
        dpa 'found futo/_glue/env.rb', COLORS[:init]
        load 'futo/_glue/env.rb'
      end
    end
  end
end

#markdown_onlyObject



137
138
139
140
141
142
143
144
145
146
# File 'lib/futo-spec.rb', line 137

def markdown_only
  pa 'markdown mode', COLORS[:init], :bright
  unless $specified_file
    raise ArgumentError, "please specify a file when using --markdown option."
  else
    test_case_lines = process_specified_file
    generate_markdown_and_print(test_case_lines)
    pa 'finished markdown.', COLORS[:init]
  end
end

#match_cases_to_chizusObject



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/futo-spec.rb', line 439

def match_cases_to_chizus
  if @chizus.length == 0
    set_all_unmatched
  else
    @cases.each_with_index do |test_case, tc_index|
      test_case.bullet_points.each do |bullet|
        dpa "matching bullet: #{bullet}", :bb
        matched = false
        @chizus.each do |chizu|
          if process_bullet_chizu_match(bullet, chizu)
            bullet.associated_commands = chizu.associated_commands
            matched = true
            break
          end
        end
        dpa "matched? #{bullet.label} : #{matched}", :bb
        if ! matched
          unless @unmatched.include? bullet.label
            dpa "couldn't find a match for #{bullet.label}", COLORS[:error]
            @unmatched << bullet.label
          end
        end
      end
    end
  end
end

#new_bullet(line) ⇒ Object



238
239
240
241
242
# File 'lib/futo-spec.rb', line 238

def new_bullet(line)
  label = line.sub(BULLET_POINTS_REGEX, '').lstrip
  #puts label
  @new_case_bullets << FutoBullet.new(label)
end

#new_label(line) ⇒ Object



244
245
246
# File 'lib/futo-spec.rb', line 244

def new_label(line)
  @new_case_label = line
end

#output_unmatched_commandsObject



541
542
543
544
545
546
547
548
549
550
551
# File 'lib/futo-spec.rb', line 541

def output_unmatched_commands
  puts
  pa "Missing chizu entries:", COLORS[:missing], :bright
  puts
  @unmatched.each do |label|
    pa "On '#{label}' do", COLORS[:missing]
    pa '  # TODO', COLORS[:missing]
    pa 'end', COLORS[:missing]
    puts
  end
end

#process_bullet_chizu_match(bullet, chizu) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/futo-spec.rb', line 466

def process_bullet_chizu_match(bullet, chizu)
  matched = false
  dpa "chizu: #{chizu.kkey}", COLORS[:init]
  if is_todo? chizu
    logd "found todo: #{chizu}", COLORS[:init]
    # todos aren't considered completed so they are unmatched
  elsif is_included_in? chizu
    #logd "found included_in: #{chizu}", COLORS[:init]
    @included_ins << chizu
  else
    if bullet.label == chizu.kkey
      matched = true
    end
  end
  return matched
end

#process_chizu_filesObject



361
362
363
# File 'lib/futo-spec.rb', line 361

def process_chizu_files
  @chizu_files.each {|ff| load_chizu_commands ff}
end

#process_specific_line(desc) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/futo-spec.rb', line 186

def process_specific_line(desc)
  spl = desc.split(':')
  desc_file = spl.first
  # allow for specifying line w/o .futo, eg `futo current:19 for current.futo:19`
  desc_file = "#{desc_file}.futo" if not desc_file.include? '.'
  line_specified = spl.last
  idx = line_specified.to_i - 1 # line numbers are 1-indexed

  puts "found specific file request: #{desc_file} at line #{line_specified} (index #{idx})"

  File.open("./futo/#{desc_file}") do |file|
    all_lines = file.readlines(chomp:true)
    specified_line = all_lines[idx]
    if is_description? specified_line
      return add_additional_lines_context_to_specified_line(all_lines, idx)
    else
      specified_line_as_arr = [ specified_line ]
      return specified_line_as_arr
    end
  end
end

#process_specified_fileObject



175
176
177
178
179
180
181
182
183
184
# File 'lib/futo-spec.rb', line 175

def process_specified_file
  dpa "process_specified_file: #{$specified_file}"
  path = "futo/#{$specified_file}"
  File.open(path) do |file|
    file_lines = file.readlines(chomp:true)
    # add one blank line to facilitate saving the last test case
    file_lines << ''
    return file_lines
  end
end

#runObject



483
484
485
486
# File 'lib/futo-spec.rb', line 483

def run
  exec_cases unless $dry_run
  output_unmatched_commands if @unmatched.length > 0
end

#run_commands_in_block_context(bullet) ⇒ Object



496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# File 'lib/futo-spec.rb', line 496

def run_commands_in_block_context(bullet)
  local_vars = {}
  bullet.associated_commands.each do |cmd|
    begin
      pa "  #{cmd}", COLORS[:support] if cmd != 'breakpoint' # agb ignore
      bind = binding
      local_vars.each_pair do |kk, vv|
        bind.local_variable_set(kk, vv)
      end
      unless cmd == 'breakpoint'
        result = bind.eval(cmd)
      else
        c = BreakpointContext.new(bind)
        c.contextual_breakpoint
      end
      dpa "result: #{result}"
      if cmd.include? '='
        new_var = cmd.split('=').first.rstrip
        unless new_var.include? '@' or new_var.include? '$'
          # don't store @ vars as a local var
          local_vars.store(new_var, result)
        end
      end
    rescue RSpec::Expectations::ExpectationNotMetError => e
      pa e, COLORS[:error], :bright
      raise e
    end
  end
end

#set_all_unmatchedObject



433
434
435
436
437
# File 'lib/futo-spec.rb', line 433

def set_all_unmatched
  @cases.each do |cc|
    cc.bullet_points.each {|bullet| @unmatched << bullet.label }
  end
end

#single_quoted_line?(line) ⇒ Boolean

Returns:

  • (Boolean)


326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/futo-spec.rb', line 326

def single_quoted_line?(line)
  single = false
  line.chars.each do |char|
    if char == '"'
      break
    end
    if char == "'"
      single = true
      break
    end
  end
  return single
end