135375f98Sopenharmony_ci#!/usr/bin/ruby
235375f98Sopenharmony_ci
335375f98Sopenharmony_ci# ==========================================
435375f98Sopenharmony_ci#   Unity Project - A Test Framework for C
535375f98Sopenharmony_ci#   Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
635375f98Sopenharmony_ci#   [Released under MIT License. Please refer to license.txt for details]
735375f98Sopenharmony_ci# ==========================================
835375f98Sopenharmony_ci
935375f98Sopenharmony_ciclass UnityTestRunnerGenerator
1035375f98Sopenharmony_ci  def initialize(options = nil)
1135375f98Sopenharmony_ci    @options = UnityTestRunnerGenerator.default_options
1235375f98Sopenharmony_ci    case options
1335375f98Sopenharmony_ci    when NilClass
1435375f98Sopenharmony_ci      @options
1535375f98Sopenharmony_ci    when String
1635375f98Sopenharmony_ci      @options.merge!(UnityTestRunnerGenerator.grab_config(options))
1735375f98Sopenharmony_ci    when Hash
1835375f98Sopenharmony_ci      # Check if some of these have been specified
1935375f98Sopenharmony_ci      @options[:has_setup] = !options[:setup_name].nil?
2035375f98Sopenharmony_ci      @options[:has_teardown] = !options[:teardown_name].nil?
2135375f98Sopenharmony_ci      @options[:has_suite_setup] = !options[:suite_setup].nil?
2235375f98Sopenharmony_ci      @options[:has_suite_teardown] = !options[:suite_teardown].nil?
2335375f98Sopenharmony_ci      @options.merge!(options)
2435375f98Sopenharmony_ci    else
2535375f98Sopenharmony_ci      raise 'If you specify arguments, it should be a filename or a hash of options'
2635375f98Sopenharmony_ci    end
2735375f98Sopenharmony_ci    require_relative 'type_sanitizer'
2835375f98Sopenharmony_ci  end
2935375f98Sopenharmony_ci
3035375f98Sopenharmony_ci  def self.default_options
3135375f98Sopenharmony_ci    {
3235375f98Sopenharmony_ci      includes: [],
3335375f98Sopenharmony_ci      defines: [],
3435375f98Sopenharmony_ci      plugins: [],
3535375f98Sopenharmony_ci      framework: :unity,
3635375f98Sopenharmony_ci      test_prefix: 'test|spec|should',
3735375f98Sopenharmony_ci      mock_prefix: 'Mock',
3835375f98Sopenharmony_ci      mock_suffix: '',
3935375f98Sopenharmony_ci      setup_name: 'setUp',
4035375f98Sopenharmony_ci      teardown_name: 'tearDown',
4135375f98Sopenharmony_ci      test_reset_name: 'resetTest',
4235375f98Sopenharmony_ci      test_verify_name: 'verifyTest',
4335375f98Sopenharmony_ci      main_name: 'main', # set to :auto to automatically generate each time
4435375f98Sopenharmony_ci      main_export_decl: '',
4535375f98Sopenharmony_ci      cmdline_args: false,
4635375f98Sopenharmony_ci      omit_begin_end: false,
4735375f98Sopenharmony_ci      use_param_tests: false,
4835375f98Sopenharmony_ci      use_system_files: true,
4935375f98Sopenharmony_ci      include_extensions: '(?:hpp|hh|H|h)',
5035375f98Sopenharmony_ci      source_extensions: '(?:cpp|cc|ino|C|c)'
5135375f98Sopenharmony_ci    }
5235375f98Sopenharmony_ci  end
5335375f98Sopenharmony_ci
5435375f98Sopenharmony_ci  def self.grab_config(config_file)
5535375f98Sopenharmony_ci    options = default_options
5635375f98Sopenharmony_ci    unless config_file.nil? || config_file.empty?
5735375f98Sopenharmony_ci      require_relative 'yaml_helper'
5835375f98Sopenharmony_ci      yaml_guts = YamlHelper.load_file(config_file)
5935375f98Sopenharmony_ci      options.merge!(yaml_guts[:unity] || yaml_guts[:cmock])
6035375f98Sopenharmony_ci      raise "No :unity or :cmock section found in #{config_file}" unless options
6135375f98Sopenharmony_ci    end
6235375f98Sopenharmony_ci    options
6335375f98Sopenharmony_ci  end
6435375f98Sopenharmony_ci
6535375f98Sopenharmony_ci  def run(input_file, output_file, options = nil)
6635375f98Sopenharmony_ci    @options.merge!(options) unless options.nil?
6735375f98Sopenharmony_ci
6835375f98Sopenharmony_ci    # pull required data from source file
6935375f98Sopenharmony_ci    source = File.read(input_file)
7035375f98Sopenharmony_ci    source = source.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
7135375f98Sopenharmony_ci    tests = find_tests(source)
7235375f98Sopenharmony_ci    headers = find_includes(source)
7335375f98Sopenharmony_ci    testfile_includes = @options[:use_system_files] ? (headers[:local] + headers[:system]) : (headers[:local])
7435375f98Sopenharmony_ci    used_mocks = find_mocks(testfile_includes)
7535375f98Sopenharmony_ci    testfile_includes = (testfile_includes - used_mocks)
7635375f98Sopenharmony_ci    testfile_includes.delete_if { |inc| inc =~ /(unity|cmock)/ }
7735375f98Sopenharmony_ci    find_setup_and_teardown(source)
7835375f98Sopenharmony_ci
7935375f98Sopenharmony_ci    # build runner file
8035375f98Sopenharmony_ci    generate(input_file, output_file, tests, used_mocks, testfile_includes)
8135375f98Sopenharmony_ci
8235375f98Sopenharmony_ci    # determine which files were used to return them
8335375f98Sopenharmony_ci    all_files_used = [input_file, output_file]
8435375f98Sopenharmony_ci    all_files_used += testfile_includes.map { |filename| "#{filename}.c" } unless testfile_includes.empty?
8535375f98Sopenharmony_ci    all_files_used += @options[:includes] unless @options[:includes].empty?
8635375f98Sopenharmony_ci    all_files_used += headers[:linkonly] unless headers[:linkonly].empty?
8735375f98Sopenharmony_ci    all_files_used.uniq
8835375f98Sopenharmony_ci  end
8935375f98Sopenharmony_ci
9035375f98Sopenharmony_ci  def generate(input_file, output_file, tests, used_mocks, testfile_includes)
9135375f98Sopenharmony_ci    File.open(output_file, 'w') do |output|
9235375f98Sopenharmony_ci      create_header(output, used_mocks, testfile_includes)
9335375f98Sopenharmony_ci      create_externs(output, tests, used_mocks)
9435375f98Sopenharmony_ci      create_mock_management(output, used_mocks)
9535375f98Sopenharmony_ci      create_setup(output)
9635375f98Sopenharmony_ci      create_teardown(output)
9735375f98Sopenharmony_ci      create_suite_setup(output)
9835375f98Sopenharmony_ci      create_suite_teardown(output)
9935375f98Sopenharmony_ci      create_reset(output)
10035375f98Sopenharmony_ci      create_run_test(output) unless tests.empty?
10135375f98Sopenharmony_ci      create_args_wrappers(output, tests)
10235375f98Sopenharmony_ci      create_main(output, input_file, tests, used_mocks)
10335375f98Sopenharmony_ci    end
10435375f98Sopenharmony_ci
10535375f98Sopenharmony_ci    return unless @options[:header_file] && !@options[:header_file].empty?
10635375f98Sopenharmony_ci
10735375f98Sopenharmony_ci    File.open(@options[:header_file], 'w') do |output|
10835375f98Sopenharmony_ci      create_h_file(output, @options[:header_file], tests, testfile_includes, used_mocks)
10935375f98Sopenharmony_ci    end
11035375f98Sopenharmony_ci  end
11135375f98Sopenharmony_ci
11235375f98Sopenharmony_ci  def find_tests(source)
11335375f98Sopenharmony_ci    tests_and_line_numbers = []
11435375f98Sopenharmony_ci
11535375f98Sopenharmony_ci    # contains characters which will be substituted from within strings, doing
11635375f98Sopenharmony_ci    # this prevents these characters from interfering with scrubbers
11735375f98Sopenharmony_ci    # @ is not a valid C character, so there should be no clashes with files genuinely containing these markers
11835375f98Sopenharmony_ci    substring_subs = { '{' => '@co@', '}' => '@cc@', ';' => '@ss@', '/' => '@fs@' }
11935375f98Sopenharmony_ci    substring_re = Regexp.union(substring_subs.keys)
12035375f98Sopenharmony_ci    substring_unsubs = substring_subs.invert                   # the inverse map will be used to fix the strings afterwords
12135375f98Sopenharmony_ci    substring_unsubs['@quote@'] = '\\"'
12235375f98Sopenharmony_ci    substring_unsubs['@apos@'] = '\\\''
12335375f98Sopenharmony_ci    substring_unre = Regexp.union(substring_unsubs.keys)
12435375f98Sopenharmony_ci    source_scrubbed = source.clone
12535375f98Sopenharmony_ci    source_scrubbed = source_scrubbed.gsub(/\\"/, '@quote@')   # hide escaped quotes to allow capture of the full string/char
12635375f98Sopenharmony_ci    source_scrubbed = source_scrubbed.gsub(/\\'/, '@apos@')    # hide escaped apostrophes to allow capture of the full string/char
12735375f98Sopenharmony_ci    source_scrubbed = source_scrubbed.gsub(/("[^"\n]*")|('[^'\n]*')/) { |s| s.gsub(substring_re, substring_subs) } # temporarily hide problematic characters within strings
12835375f98Sopenharmony_ci    source_scrubbed = source_scrubbed.gsub(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '')  # remove line comments that comment out the start of blocks
12935375f98Sopenharmony_ci    source_scrubbed = source_scrubbed.gsub(/\/\*.*?\*\//m, '')                     # remove block comments
13035375f98Sopenharmony_ci    source_scrubbed = source_scrubbed.gsub(/\/\/.*$/, '')                          # remove line comments (all that remain)
13135375f98Sopenharmony_ci    lines = source_scrubbed.split(/(^\s*\#.*$) | (;|\{|\}) /x)                     # Treat preprocessor directives as a logical line. Match ;, {, and } as end of lines
13235375f98Sopenharmony_ci                           .map { |line| line.gsub(substring_unre, substring_unsubs) } # unhide the problematic characters previously removed
13335375f98Sopenharmony_ci
13435375f98Sopenharmony_ci    lines.each_with_index do |line, _index|
13535375f98Sopenharmony_ci      # find tests
13635375f98Sopenharmony_ci      next unless line =~ /^((?:\s*(?:TEST_(?:CASE|RANGE|MATRIX))\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]}).*)\s*\(\s*(.*)\s*\)/m
13735375f98Sopenharmony_ci      next unless line =~ /^((?:\s*(?:TEST_(?:CASE|RANGE|MATRIX))\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]})\w*)\s*\(\s*(.*)\s*\)/m
13835375f98Sopenharmony_ci
13935375f98Sopenharmony_ci      arguments = Regexp.last_match(1)
14035375f98Sopenharmony_ci      name = Regexp.last_match(2)
14135375f98Sopenharmony_ci      call = Regexp.last_match(3)
14235375f98Sopenharmony_ci      params = Regexp.last_match(4)
14335375f98Sopenharmony_ci      args = nil
14435375f98Sopenharmony_ci
14535375f98Sopenharmony_ci      if @options[:use_param_tests] && !arguments.empty?
14635375f98Sopenharmony_ci        args = []
14735375f98Sopenharmony_ci        type_and_args = arguments.split(/TEST_(CASE|RANGE|MATRIX)/)
14835375f98Sopenharmony_ci        (1...type_and_args.length).step(2).each do |i|
14935375f98Sopenharmony_ci          case type_and_args[i]
15035375f98Sopenharmony_ci          when 'CASE'
15135375f98Sopenharmony_ci            args << type_and_args[i + 1].sub(/^\s*\(\s*(.*?)\s*\)\s*$/m, '\1')
15235375f98Sopenharmony_ci
15335375f98Sopenharmony_ci          when 'RANGE'
15435375f98Sopenharmony_ci            args += type_and_args[i + 1].scan(/(\[|<)\s*(-?\d+.?\d*)\s*,\s*(-?\d+.?\d*)\s*,\s*(-?\d+.?\d*)\s*(\]|>)/m).map do |arg_values_str|
15535375f98Sopenharmony_ci              exclude_end = arg_values_str[0] == '<' && arg_values_str[-1] == '>'
15635375f98Sopenharmony_ci              arg_values_str[1...-1].map do |arg_value_str|
15735375f98Sopenharmony_ci                arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i
15835375f98Sopenharmony_ci              end.push(exclude_end)
15935375f98Sopenharmony_ci            end.map do |arg_values|
16035375f98Sopenharmony_ci              Range.new(arg_values[0], arg_values[1], arg_values[3]).step(arg_values[2]).to_a
16135375f98Sopenharmony_ci            end.reduce(nil) do |result, arg_range_expanded|
16235375f98Sopenharmony_ci              result.nil? ? arg_range_expanded.map { |a| [a] } : result.product(arg_range_expanded)
16335375f98Sopenharmony_ci            end.map do |arg_combinations|
16435375f98Sopenharmony_ci              arg_combinations.flatten.join(', ')
16535375f98Sopenharmony_ci            end
16635375f98Sopenharmony_ci
16735375f98Sopenharmony_ci          when 'MATRIX'
16835375f98Sopenharmony_ci            single_arg_regex_string = /(?:(?:"(?:\\"|[^\\])*?")+|(?:'\\?.')+|(?:[^\s\]\["',]|\[[\d\S_-]+\])+)/.source
16935375f98Sopenharmony_ci            args_regex = /\[((?:\s*#{single_arg_regex_string}\s*,?)*(?:\s*#{single_arg_regex_string})?\s*)\]/m
17035375f98Sopenharmony_ci            arg_elements_regex = /\s*(#{single_arg_regex_string})\s*,\s*/m
17135375f98Sopenharmony_ci
17235375f98Sopenharmony_ci            args += type_and_args[i + 1].scan(args_regex).flatten.map do |arg_values_str|
17335375f98Sopenharmony_ci              ("#{arg_values_str},").scan(arg_elements_regex)
17435375f98Sopenharmony_ci            end.reduce do |result, arg_range_expanded|
17535375f98Sopenharmony_ci              result.product(arg_range_expanded)
17635375f98Sopenharmony_ci            end.map do |arg_combinations|
17735375f98Sopenharmony_ci              arg_combinations.flatten.join(', ')
17835375f98Sopenharmony_ci            end
17935375f98Sopenharmony_ci          end
18035375f98Sopenharmony_ci        end
18135375f98Sopenharmony_ci      end
18235375f98Sopenharmony_ci
18335375f98Sopenharmony_ci      tests_and_line_numbers << { test: name, args: args, call: call, params: params, line_number: 0 }
18435375f98Sopenharmony_ci    end
18535375f98Sopenharmony_ci
18635375f98Sopenharmony_ci    tests_and_line_numbers.uniq! { |v| v[:test] }
18735375f98Sopenharmony_ci
18835375f98Sopenharmony_ci    # determine line numbers and create tests to run
18935375f98Sopenharmony_ci    source_lines = source.split("\n")
19035375f98Sopenharmony_ci    source_index = 0
19135375f98Sopenharmony_ci    tests_and_line_numbers.size.times do |i|
19235375f98Sopenharmony_ci      source_lines[source_index..].each_with_index do |line, index|
19335375f98Sopenharmony_ci        next unless line =~ /\s+#{tests_and_line_numbers[i][:test]}(?:\s|\()/
19435375f98Sopenharmony_ci
19535375f98Sopenharmony_ci        source_index += index
19635375f98Sopenharmony_ci        tests_and_line_numbers[i][:line_number] = source_index + 1
19735375f98Sopenharmony_ci        break
19835375f98Sopenharmony_ci      end
19935375f98Sopenharmony_ci    end
20035375f98Sopenharmony_ci
20135375f98Sopenharmony_ci    tests_and_line_numbers
20235375f98Sopenharmony_ci  end
20335375f98Sopenharmony_ci
20435375f98Sopenharmony_ci  def find_includes(source)
20535375f98Sopenharmony_ci    # remove comments (block and line, in three steps to ensure correct precedence)
20635375f98Sopenharmony_ci    source.gsub!(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '')  # remove line comments that comment out the start of blocks
20735375f98Sopenharmony_ci    source.gsub!(/\/\*.*?\*\//m, '')                     # remove block comments
20835375f98Sopenharmony_ci    source.gsub!(/\/\/.*$/, '')                          # remove line comments (all that remain)
20935375f98Sopenharmony_ci
21035375f98Sopenharmony_ci    # parse out includes
21135375f98Sopenharmony_ci    {
21235375f98Sopenharmony_ci      local: source.scan(/^\s*#include\s+"\s*(.+\.#{@options[:include_extensions]})\s*"/).flatten,
21335375f98Sopenharmony_ci      system: source.scan(/^\s*#include\s+<\s*(.+)\s*>/).flatten.map { |inc| "<#{inc}>" },
21435375f98Sopenharmony_ci      linkonly: source.scan(/^TEST_SOURCE_FILE\(\s*"\s*(.+\.#{@options[:source_extensions]})\s*"/).flatten
21535375f98Sopenharmony_ci    }
21635375f98Sopenharmony_ci  end
21735375f98Sopenharmony_ci
21835375f98Sopenharmony_ci  def find_mocks(includes)
21935375f98Sopenharmony_ci    mock_headers = []
22035375f98Sopenharmony_ci    includes.each do |include_path|
22135375f98Sopenharmony_ci      include_file = File.basename(include_path)
22235375f98Sopenharmony_ci      mock_headers << include_path if include_file =~ /^#{@options[:mock_prefix]}.*#{@options[:mock_suffix]}\.h$/i
22335375f98Sopenharmony_ci    end
22435375f98Sopenharmony_ci    mock_headers
22535375f98Sopenharmony_ci  end
22635375f98Sopenharmony_ci
22735375f98Sopenharmony_ci  def find_setup_and_teardown(source)
22835375f98Sopenharmony_ci    @options[:has_setup] = source =~ /void\s+#{@options[:setup_name]}\s*\(/
22935375f98Sopenharmony_ci    @options[:has_teardown] = source =~ /void\s+#{@options[:teardown_name]}\s*\(/
23035375f98Sopenharmony_ci    @options[:has_suite_setup] ||= (source =~ /void\s+suiteSetUp\s*\(/)
23135375f98Sopenharmony_ci    @options[:has_suite_teardown] ||= (source =~ /int\s+suiteTearDown\s*\(int\s+([a-zA-Z0-9_])+\s*\)/)
23235375f98Sopenharmony_ci  end
23335375f98Sopenharmony_ci
23435375f98Sopenharmony_ci  def create_header(output, mocks, testfile_includes = [])
23535375f98Sopenharmony_ci    output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */')
23635375f98Sopenharmony_ci    output.puts("\n/*=======Automagically Detected Files To Include=====*/")
23735375f98Sopenharmony_ci    output.puts('extern "C" {') if @options[:externcincludes]
23835375f98Sopenharmony_ci    output.puts("#include \"#{@options[:framework]}.h\"")
23935375f98Sopenharmony_ci    output.puts('#include "cmock.h"') unless mocks.empty?
24035375f98Sopenharmony_ci    output.puts('}') if @options[:externcincludes]
24135375f98Sopenharmony_ci    if @options[:defines] && !@options[:defines].empty?
24235375f98Sopenharmony_ci      output.puts("/* injected defines for unity settings, etc */")
24335375f98Sopenharmony_ci      @options[:defines].each do |d| 
24435375f98Sopenharmony_ci        def_only = d.match(/(\w+).*/)[1]
24535375f98Sopenharmony_ci        output.puts("#ifndef #{def_only}\n#define #{d}\n#endif /* #{def_only} */")
24635375f98Sopenharmony_ci      end
24735375f98Sopenharmony_ci    end
24835375f98Sopenharmony_ci    if @options[:header_file] && !@options[:header_file].empty?
24935375f98Sopenharmony_ci      output.puts("#include \"#{File.basename(@options[:header_file])}\"")
25035375f98Sopenharmony_ci    else
25135375f98Sopenharmony_ci      @options[:includes].flatten.uniq.compact.each do |inc|
25235375f98Sopenharmony_ci        output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}")
25335375f98Sopenharmony_ci      end
25435375f98Sopenharmony_ci      testfile_includes.each do |inc|
25535375f98Sopenharmony_ci        output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}")
25635375f98Sopenharmony_ci      end
25735375f98Sopenharmony_ci    end
25835375f98Sopenharmony_ci    output.puts('extern "C" {') if @options[:externcincludes]
25935375f98Sopenharmony_ci    mocks.each do |mock|
26035375f98Sopenharmony_ci      output.puts("#include \"#{mock}\"")
26135375f98Sopenharmony_ci    end
26235375f98Sopenharmony_ci    output.puts('}') if @options[:externcincludes]
26335375f98Sopenharmony_ci    output.puts('#include "CException.h"') if @options[:plugins].include?(:cexception)
26435375f98Sopenharmony_ci
26535375f98Sopenharmony_ci    return unless @options[:enforce_strict_ordering]
26635375f98Sopenharmony_ci
26735375f98Sopenharmony_ci    output.puts('')
26835375f98Sopenharmony_ci    output.puts('int GlobalExpectCount;')
26935375f98Sopenharmony_ci    output.puts('int GlobalVerifyOrder;')
27035375f98Sopenharmony_ci    output.puts('char* GlobalOrderError;')
27135375f98Sopenharmony_ci  end
27235375f98Sopenharmony_ci
27335375f98Sopenharmony_ci  def create_externs(output, tests, _mocks)
27435375f98Sopenharmony_ci    output.puts("\n/*=======External Functions This Runner Calls=====*/")
27535375f98Sopenharmony_ci    output.puts("extern void #{@options[:setup_name]}(void);")
27635375f98Sopenharmony_ci    output.puts("extern void #{@options[:teardown_name]}(void);")
27735375f98Sopenharmony_ci    output.puts("\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif") if @options[:externc]
27835375f98Sopenharmony_ci    tests.each do |test|
27935375f98Sopenharmony_ci      output.puts("extern void #{test[:test]}(#{test[:call] || 'void'});")
28035375f98Sopenharmony_ci    end
28135375f98Sopenharmony_ci    output.puts("#ifdef __cplusplus\n}\n#endif") if @options[:externc]
28235375f98Sopenharmony_ci    output.puts('')
28335375f98Sopenharmony_ci  end
28435375f98Sopenharmony_ci
28535375f98Sopenharmony_ci  def create_mock_management(output, mock_headers)
28635375f98Sopenharmony_ci    output.puts("\n/*=======Mock Management=====*/")
28735375f98Sopenharmony_ci    output.puts('static void CMock_Init(void)')
28835375f98Sopenharmony_ci    output.puts('{')
28935375f98Sopenharmony_ci
29035375f98Sopenharmony_ci    if @options[:enforce_strict_ordering]
29135375f98Sopenharmony_ci      output.puts('  GlobalExpectCount = 0;')
29235375f98Sopenharmony_ci      output.puts('  GlobalVerifyOrder = 0;')
29335375f98Sopenharmony_ci      output.puts('  GlobalOrderError = NULL;')
29435375f98Sopenharmony_ci    end
29535375f98Sopenharmony_ci
29635375f98Sopenharmony_ci    mocks = mock_headers.map { |mock| File.basename(mock, '.*') }
29735375f98Sopenharmony_ci    mocks.each do |mock|
29835375f98Sopenharmony_ci      mock_clean = TypeSanitizer.sanitize_c_identifier(mock)
29935375f98Sopenharmony_ci      output.puts("  #{mock_clean}_Init();")
30035375f98Sopenharmony_ci    end
30135375f98Sopenharmony_ci    output.puts("}\n")
30235375f98Sopenharmony_ci
30335375f98Sopenharmony_ci    output.puts('static void CMock_Verify(void)')
30435375f98Sopenharmony_ci    output.puts('{')
30535375f98Sopenharmony_ci    mocks.each do |mock|
30635375f98Sopenharmony_ci      mock_clean = TypeSanitizer.sanitize_c_identifier(mock)
30735375f98Sopenharmony_ci      output.puts("  #{mock_clean}_Verify();")
30835375f98Sopenharmony_ci    end
30935375f98Sopenharmony_ci    output.puts("}\n")
31035375f98Sopenharmony_ci
31135375f98Sopenharmony_ci    output.puts('static void CMock_Destroy(void)')
31235375f98Sopenharmony_ci    output.puts('{')
31335375f98Sopenharmony_ci    mocks.each do |mock|
31435375f98Sopenharmony_ci      mock_clean = TypeSanitizer.sanitize_c_identifier(mock)
31535375f98Sopenharmony_ci      output.puts("  #{mock_clean}_Destroy();")
31635375f98Sopenharmony_ci    end
31735375f98Sopenharmony_ci    output.puts("}\n")
31835375f98Sopenharmony_ci  end
31935375f98Sopenharmony_ci
32035375f98Sopenharmony_ci  def create_setup(output)
32135375f98Sopenharmony_ci    return if @options[:has_setup]
32235375f98Sopenharmony_ci
32335375f98Sopenharmony_ci    output.puts("\n/*=======Setup (stub)=====*/")
32435375f98Sopenharmony_ci    output.puts("void #{@options[:setup_name]}(void) {}")
32535375f98Sopenharmony_ci  end
32635375f98Sopenharmony_ci
32735375f98Sopenharmony_ci  def create_teardown(output)
32835375f98Sopenharmony_ci    return if @options[:has_teardown]
32935375f98Sopenharmony_ci
33035375f98Sopenharmony_ci    output.puts("\n/*=======Teardown (stub)=====*/")
33135375f98Sopenharmony_ci    output.puts("void #{@options[:teardown_name]}(void) {}")
33235375f98Sopenharmony_ci  end
33335375f98Sopenharmony_ci
33435375f98Sopenharmony_ci  def create_suite_setup(output)
33535375f98Sopenharmony_ci    return if @options[:suite_setup].nil?
33635375f98Sopenharmony_ci
33735375f98Sopenharmony_ci    output.puts("\n/*=======Suite Setup=====*/")
33835375f98Sopenharmony_ci    output.puts('void suiteSetUp(void)')
33935375f98Sopenharmony_ci    output.puts('{')
34035375f98Sopenharmony_ci    output.puts(@options[:suite_setup])
34135375f98Sopenharmony_ci    output.puts('}')
34235375f98Sopenharmony_ci  end
34335375f98Sopenharmony_ci
34435375f98Sopenharmony_ci  def create_suite_teardown(output)
34535375f98Sopenharmony_ci    return if @options[:suite_teardown].nil?
34635375f98Sopenharmony_ci
34735375f98Sopenharmony_ci    output.puts("\n/*=======Suite Teardown=====*/")
34835375f98Sopenharmony_ci    output.puts('int suiteTearDown(int num_failures)')
34935375f98Sopenharmony_ci    output.puts('{')
35035375f98Sopenharmony_ci    output.puts(@options[:suite_teardown])
35135375f98Sopenharmony_ci    output.puts('}')
35235375f98Sopenharmony_ci  end
35335375f98Sopenharmony_ci
35435375f98Sopenharmony_ci  def create_reset(output)
35535375f98Sopenharmony_ci    output.puts("\n/*=======Test Reset Options=====*/")
35635375f98Sopenharmony_ci    output.puts("void #{@options[:test_reset_name]}(void);")
35735375f98Sopenharmony_ci    output.puts("void #{@options[:test_reset_name]}(void)")
35835375f98Sopenharmony_ci    output.puts('{')
35935375f98Sopenharmony_ci    output.puts("  #{@options[:teardown_name]}();")
36035375f98Sopenharmony_ci    output.puts('  CMock_Verify();')
36135375f98Sopenharmony_ci    output.puts('  CMock_Destroy();')
36235375f98Sopenharmony_ci    output.puts('  CMock_Init();')
36335375f98Sopenharmony_ci    output.puts("  #{@options[:setup_name]}();")
36435375f98Sopenharmony_ci    output.puts('}')
36535375f98Sopenharmony_ci    output.puts("void #{@options[:test_verify_name]}(void);")
36635375f98Sopenharmony_ci    output.puts("void #{@options[:test_verify_name]}(void)")
36735375f98Sopenharmony_ci    output.puts('{')
36835375f98Sopenharmony_ci    output.puts('  CMock_Verify();')
36935375f98Sopenharmony_ci    output.puts('}')
37035375f98Sopenharmony_ci  end
37135375f98Sopenharmony_ci
37235375f98Sopenharmony_ci  def create_run_test(output)
37335375f98Sopenharmony_ci    require 'erb'
37435375f98Sopenharmony_ci    file = File.read(File.join(__dir__, 'run_test.erb'))
37535375f98Sopenharmony_ci    template = ERB.new(file, trim_mode: '<>')
37635375f98Sopenharmony_ci    output.puts("\n#{template.result(binding)}")
37735375f98Sopenharmony_ci  end
37835375f98Sopenharmony_ci
37935375f98Sopenharmony_ci  def create_args_wrappers(output, tests)
38035375f98Sopenharmony_ci    return unless @options[:use_param_tests]
38135375f98Sopenharmony_ci
38235375f98Sopenharmony_ci    output.puts("\n/*=======Parameterized Test Wrappers=====*/")
38335375f98Sopenharmony_ci    tests.each do |test|
38435375f98Sopenharmony_ci      next if test[:args].nil? || test[:args].empty?
38535375f98Sopenharmony_ci
38635375f98Sopenharmony_ci      test[:args].each.with_index(1) do |args, idx|
38735375f98Sopenharmony_ci        output.puts("static void runner_args#{idx}_#{test[:test]}(void)")
38835375f98Sopenharmony_ci        output.puts('{')
38935375f98Sopenharmony_ci        output.puts("    #{test[:test]}(#{args});")
39035375f98Sopenharmony_ci        output.puts("}\n")
39135375f98Sopenharmony_ci      end
39235375f98Sopenharmony_ci    end
39335375f98Sopenharmony_ci  end
39435375f98Sopenharmony_ci
39535375f98Sopenharmony_ci  def create_main(output, filename, tests, used_mocks)
39635375f98Sopenharmony_ci    output.puts("\n/*=======MAIN=====*/")
39735375f98Sopenharmony_ci    main_name = @options[:main_name].to_sym == :auto ? "main_#{filename.gsub('.c', '')}" : (@options[:main_name]).to_s
39835375f98Sopenharmony_ci    if @options[:cmdline_args]
39935375f98Sopenharmony_ci      if main_name != 'main'
40035375f98Sopenharmony_ci        output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv);")
40135375f98Sopenharmony_ci      end
40235375f98Sopenharmony_ci      output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv)")
40335375f98Sopenharmony_ci      output.puts('{')
40435375f98Sopenharmony_ci      output.puts('  int parse_status = UnityParseOptions(argc, argv);')
40535375f98Sopenharmony_ci      output.puts('  if (parse_status != 0)')
40635375f98Sopenharmony_ci      output.puts('  {')
40735375f98Sopenharmony_ci      output.puts('    if (parse_status < 0)')
40835375f98Sopenharmony_ci      output.puts('    {')
40935375f98Sopenharmony_ci      output.puts("      UnityPrint(\"#{filename.gsub('.c', '').gsub(/\\/, '\\\\\\')}.\");")
41035375f98Sopenharmony_ci      output.puts('      UNITY_PRINT_EOL();')
41135375f98Sopenharmony_ci      tests.each do |test|
41235375f98Sopenharmony_ci        if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty?
41335375f98Sopenharmony_ci          output.puts("      UnityPrint(\"  #{test[:test]}\");")
41435375f98Sopenharmony_ci          output.puts('      UNITY_PRINT_EOL();')
41535375f98Sopenharmony_ci        else
41635375f98Sopenharmony_ci          test[:args].each do |args|
41735375f98Sopenharmony_ci            output.puts("      UnityPrint(\"  #{test[:test]}(#{args})\");")
41835375f98Sopenharmony_ci            output.puts('      UNITY_PRINT_EOL();')
41935375f98Sopenharmony_ci          end
42035375f98Sopenharmony_ci        end
42135375f98Sopenharmony_ci      end
42235375f98Sopenharmony_ci      output.puts('      return 0;')
42335375f98Sopenharmony_ci      output.puts('    }')
42435375f98Sopenharmony_ci      output.puts('    return parse_status;')
42535375f98Sopenharmony_ci      output.puts('  }')
42635375f98Sopenharmony_ci    else
42735375f98Sopenharmony_ci      main_return = @options[:omit_begin_end] ? 'void' : 'int'
42835375f98Sopenharmony_ci      if main_name != 'main'
42935375f98Sopenharmony_ci        output.puts("#{@options[:main_export_decl]} #{main_return} #{main_name}(void);")
43035375f98Sopenharmony_ci      end
43135375f98Sopenharmony_ci      output.puts("#{main_return} #{main_name}(void)")
43235375f98Sopenharmony_ci      output.puts('{')
43335375f98Sopenharmony_ci    end
43435375f98Sopenharmony_ci    output.puts('  suiteSetUp();') if @options[:has_suite_setup]
43535375f98Sopenharmony_ci    if @options[:omit_begin_end]
43635375f98Sopenharmony_ci      output.puts("  UnitySetTestFile(\"#{filename.gsub(/\\/, '\\\\\\')}\");")
43735375f98Sopenharmony_ci    else
43835375f98Sopenharmony_ci      output.puts("  UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");")
43935375f98Sopenharmony_ci    end
44035375f98Sopenharmony_ci    tests.each do |test|
44135375f98Sopenharmony_ci      if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty?
44235375f98Sopenharmony_ci        output.puts("  run_test(#{test[:test]}, \"#{test[:test]}\", #{test[:line_number]});")
44335375f98Sopenharmony_ci      else
44435375f98Sopenharmony_ci        test[:args].each.with_index(1) do |args, idx|
44535375f98Sopenharmony_ci          wrapper = "runner_args#{idx}_#{test[:test]}"
44635375f98Sopenharmony_ci          testname = "#{test[:test]}(#{args})".dump
44735375f98Sopenharmony_ci          output.puts("  run_test(#{wrapper}, #{testname}, #{test[:line_number]});")
44835375f98Sopenharmony_ci        end
44935375f98Sopenharmony_ci      end
45035375f98Sopenharmony_ci    end
45135375f98Sopenharmony_ci    output.puts
45235375f98Sopenharmony_ci    output.puts('  CMock_Guts_MemFreeFinal();') unless used_mocks.empty?
45335375f98Sopenharmony_ci    if @options[:has_suite_teardown]
45435375f98Sopenharmony_ci      if @options[:omit_begin_end]
45535375f98Sopenharmony_ci        output.puts('  (void) suite_teardown(0);')
45635375f98Sopenharmony_ci      else
45735375f98Sopenharmony_ci        output.puts('  return suiteTearDown(UnityEnd());')
45835375f98Sopenharmony_ci      end
45935375f98Sopenharmony_ci    else
46035375f98Sopenharmony_ci      output.puts('  return UnityEnd();') unless @options[:omit_begin_end]
46135375f98Sopenharmony_ci    end
46235375f98Sopenharmony_ci    output.puts('}')
46335375f98Sopenharmony_ci  end
46435375f98Sopenharmony_ci
46535375f98Sopenharmony_ci  def create_h_file(output, filename, tests, testfile_includes, used_mocks)
46635375f98Sopenharmony_ci    filename = File.basename(filename).gsub(/[-\/\\.,\s]/, '_').upcase
46735375f98Sopenharmony_ci    output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */')
46835375f98Sopenharmony_ci    output.puts("#ifndef _#{filename}")
46935375f98Sopenharmony_ci    output.puts("#define _#{filename}\n\n")
47035375f98Sopenharmony_ci    output.puts("#include \"#{@options[:framework]}.h\"")
47135375f98Sopenharmony_ci    output.puts('#include "cmock.h"') unless used_mocks.empty?
47235375f98Sopenharmony_ci    @options[:includes].flatten.uniq.compact.each do |inc|
47335375f98Sopenharmony_ci      output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}")
47435375f98Sopenharmony_ci    end
47535375f98Sopenharmony_ci    testfile_includes.each do |inc|
47635375f98Sopenharmony_ci      output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}")
47735375f98Sopenharmony_ci    end
47835375f98Sopenharmony_ci    output.puts "\n"
47935375f98Sopenharmony_ci    tests.each do |test|
48035375f98Sopenharmony_ci      if test[:params].nil? || test[:params].empty?
48135375f98Sopenharmony_ci        output.puts("void #{test[:test]}(void);")
48235375f98Sopenharmony_ci      else
48335375f98Sopenharmony_ci        output.puts("void #{test[:test]}(#{test[:params]});")
48435375f98Sopenharmony_ci      end
48535375f98Sopenharmony_ci    end
48635375f98Sopenharmony_ci    output.puts("#endif\n\n")
48735375f98Sopenharmony_ci  end
48835375f98Sopenharmony_ciend
48935375f98Sopenharmony_ci
49035375f98Sopenharmony_ciif $0 == __FILE__
49135375f98Sopenharmony_ci  options = { includes: [] }
49235375f98Sopenharmony_ci
49335375f98Sopenharmony_ci  # parse out all the options first (these will all be removed as we go)
49435375f98Sopenharmony_ci  ARGV.reject! do |arg|
49535375f98Sopenharmony_ci    case arg
49635375f98Sopenharmony_ci    when '-cexception'
49735375f98Sopenharmony_ci      options[:plugins] = [:cexception]
49835375f98Sopenharmony_ci      true
49935375f98Sopenharmony_ci    when '-externcincludes'
50035375f98Sopenharmony_ci      options[:externcincludes] = true
50135375f98Sopenharmony_ci      true
50235375f98Sopenharmony_ci    when /\.*\.ya?ml$/
50335375f98Sopenharmony_ci      options = UnityTestRunnerGenerator.grab_config(arg)
50435375f98Sopenharmony_ci      true
50535375f98Sopenharmony_ci    when /--(\w+)="?(.*)"?/
50635375f98Sopenharmony_ci      options[Regexp.last_match(1).to_sym] = Regexp.last_match(2)
50735375f98Sopenharmony_ci      true
50835375f98Sopenharmony_ci    when /\.*\.(?:hpp|hh|H|h)$/
50935375f98Sopenharmony_ci      options[:includes] << arg
51035375f98Sopenharmony_ci      true
51135375f98Sopenharmony_ci    else false
51235375f98Sopenharmony_ci    end
51335375f98Sopenharmony_ci  end
51435375f98Sopenharmony_ci
51535375f98Sopenharmony_ci  # make sure there is at least one parameter left (the input file)
51635375f98Sopenharmony_ci  unless ARGV[0]
51735375f98Sopenharmony_ci    puts ["\nusage: ruby #{__FILE__} (files) (options) input_test_file (output)",
51835375f98Sopenharmony_ci          "\n  input_test_file         - this is the C file you want to create a runner for",
51935375f98Sopenharmony_ci          '  output                  - this is the name of the runner file to generate',
52035375f98Sopenharmony_ci          '                            defaults to (input_test_file)_Runner',
52135375f98Sopenharmony_ci          '  files:',
52235375f98Sopenharmony_ci          '    *.yml / *.yaml        - loads configuration from here in :unity or :cmock',
52335375f98Sopenharmony_ci          '    *.h                   - header files are added as #includes in runner',
52435375f98Sopenharmony_ci          '  options:',
52535375f98Sopenharmony_ci          '    -cexception           - include cexception support',
52635375f98Sopenharmony_ci          '    -externc              - add extern "C" for cpp support',
52735375f98Sopenharmony_ci          '    --setup_name=""       - redefine setUp func name to something else',
52835375f98Sopenharmony_ci          '    --teardown_name=""    - redefine tearDown func name to something else',
52935375f98Sopenharmony_ci          '    --main_name=""        - redefine main func name to something else',
53035375f98Sopenharmony_ci          '    --test_prefix=""      - redefine test prefix from default test|spec|should',
53135375f98Sopenharmony_ci          '    --test_reset_name=""  - redefine resetTest func name to something else',
53235375f98Sopenharmony_ci          '    --test_verify_name="" - redefine verifyTest func name to something else',
53335375f98Sopenharmony_ci          '    --suite_setup=""      - code to execute for setup of entire suite',
53435375f98Sopenharmony_ci          '    --suite_teardown=""   - code to execute for teardown of entire suite',
53535375f98Sopenharmony_ci          '    --use_param_tests=1   - enable parameterized tests (disabled by default)',
53635375f98Sopenharmony_ci          '    --omit_begin_end=1    - omit calls to UnityBegin and UnityEnd (disabled by default)',
53735375f98Sopenharmony_ci          '    --header_file=""      - path/name of test header file to generate too'].join("\n")
53835375f98Sopenharmony_ci    exit 1
53935375f98Sopenharmony_ci  end
54035375f98Sopenharmony_ci
54135375f98Sopenharmony_ci  # create the default test runner name if not specified
54235375f98Sopenharmony_ci  ARGV[1] = ARGV[0].gsub('.c', '_Runner.c') unless ARGV[1]
54335375f98Sopenharmony_ci
54435375f98Sopenharmony_ci  UnityTestRunnerGenerator.new(options).run(ARGV[0], ARGV[1])
54535375f98Sopenharmony_ciend
546