feat(cJSON): Update cJSON library to V1.7.6

Source: https://github.com/DaveGamble/cJSON.git
This commit is contained in:
zhangjunhao
2018-04-20 20:11:12 +08:00
parent 0e2445bfdb
commit 1dcf28de32
215 changed files with 32341 additions and 668 deletions

View File

@ -0,0 +1,118 @@
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
if RUBY_PLATFORM =~ /(win|w)32$/
begin
require 'Win32API'
rescue LoadError
puts 'ERROR! "Win32API" library not found'
puts '"Win32API" is required for colour on a windows machine'
puts ' try => "gem install Win32API" on the command line'
puts
end
# puts
# puts 'Windows Environment Detected...'
# puts 'Win32API Library Found.'
# puts
end
class ColourCommandLine
def initialize
return unless RUBY_PLATFORM =~ /(win|w)32$/
get_std_handle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
@set_console_txt_attrb =
Win32API.new('kernel32', 'SetConsoleTextAttribute', %w(L N), 'I')
@hout = get_std_handle.call(-11)
end
def change_to(new_colour)
if RUBY_PLATFORM =~ /(win|w)32$/
@set_console_txt_attrb.call(@hout, win32_colour(new_colour))
else
"\033[30;#{posix_colour(new_colour)};22m"
end
end
def win32_colour(colour)
case colour
when :black then 0
when :dark_blue then 1
when :dark_green then 2
when :dark_cyan then 3
when :dark_red then 4
when :dark_purple then 5
when :dark_yellow, :narrative then 6
when :default_white, :default, :dark_white then 7
when :silver then 8
when :blue then 9
when :green, :success then 10
when :cyan, :output then 11
when :red, :failure then 12
when :purple then 13
when :yellow then 14
when :white then 15
else
0
end
end
def posix_colour(colour)
# ANSI Escape Codes - Foreground colors
# | Code | Color |
# | 39 | Default foreground color |
# | 30 | Black |
# | 31 | Red |
# | 32 | Green |
# | 33 | Yellow |
# | 34 | Blue |
# | 35 | Magenta |
# | 36 | Cyan |
# | 37 | Light gray |
# | 90 | Dark gray |
# | 91 | Light red |
# | 92 | Light green |
# | 93 | Light yellow |
# | 94 | Light blue |
# | 95 | Light magenta |
# | 96 | Light cyan |
# | 97 | White |
case colour
when :black then 30
when :red, :failure then 31
when :green, :success then 32
when :yellow then 33
when :blue, :narrative then 34
when :purple, :magenta then 35
when :cyan, :output then 36
when :white, :default_white then 37
when :default then 39
else
39
end
end
def out_c(mode, colour, str)
case RUBY_PLATFORM
when /(win|w)32$/
change_to(colour)
$stdout.puts str if mode == :puts
$stdout.print str if mode == :print
change_to(:default_white)
else
$stdout.puts("#{change_to(colour)}#{str}\033[0m") if mode == :puts
$stdout.print("#{change_to(colour)}#{str}\033[0m") if mode == :print
end
end
end # ColourCommandLine
def colour_puts(role, str)
ColourCommandLine.new.out_c(:puts, role, str)
end
def colour_print(role, str)
ColourCommandLine.new.out_c(:print, role, str)
end

View File

@ -0,0 +1,39 @@
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
require "#{File.expand_path(File.dirname(__FILE__))}/colour_prompt"
$colour_output = true
def report(message)
if !$colour_output
$stdout.puts(message)
else
message = message.join('\n') if message.class == Array
message.each_line do |line|
line.chomp!
colour = case line
when /(?:total\s+)?tests:?\s+(\d+)\s+(?:total\s+)?failures:?\s+\d+\s+Ignored:?/i
Regexp.last_match(1).to_i.zero? ? :green : :red
when /PASS/
:green
when /^OK$/
:green
when /(?:FAIL|ERROR)/
:red
when /IGNORE/
:yellow
when /^(?:Creating|Compiling|Linking)/
:white
else
:silver
end
colour_puts(colour, line)
end
end
$stdout.flush
$stderr.flush
end

View File

@ -0,0 +1,36 @@
#this is a sample configuration file for generate_module
#you would use it by calling generate_module with the -ygenerate_config.yml option
#files like this are useful for customizing generate_module to your environment
:generate_module:
:defaults:
#these defaults are used in place of any missing options at the command line
:path_src: ../src/
:path_inc: ../src/
:path_tst: ../test/
:update_svn: true
:includes:
#use [] for no additional includes, otherwise list the includes on separate lines
:src:
- Defs.h
- Board.h
:inc: []
:tst:
- Defs.h
- Board.h
- Exception.h
:boilerplates:
#these are inserted at the top of generated files.
#just comment out or remove if not desired.
#use %1$s where you would like the file name to appear (path/extension not included)
:src: |
//-------------------------------------------
// %1$s.c
//-------------------------------------------
:inc: |
//-------------------------------------------
// %1$s.h
//-------------------------------------------
:tst: |
//-------------------------------------------
// Test%1$s.c : Units tests for %1$s.c
//-------------------------------------------

View File

@ -0,0 +1,308 @@
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
# This script creates all the files with start code necessary for a new module.
# A simple module only requires a source file, header file, and test file.
# Triad modules require a source, header, and test file for each triad type (like model, conductor, and hardware).
require 'rubygems'
require 'fileutils'
require 'pathname'
# TEMPLATE_TST
TEMPLATE_TST ||= '#include "unity.h"
%2$s#include "%1$s.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_%1$s_NeedToImplement(void)
{
TEST_IGNORE_MESSAGE("Need to Implement %1$s");
}
'.freeze
# TEMPLATE_SRC
TEMPLATE_SRC ||= '%2$s#include "%1$s.h"
'.freeze
# TEMPLATE_INC
TEMPLATE_INC ||= '#ifndef _%3$s_H
#define _%3$s_H
%2$s
#endif // _%3$s_H
'.freeze
class UnityModuleGenerator
############################
def initialize(options = nil)
here = File.expand_path(File.dirname(__FILE__)) + '/'
@options = UnityModuleGenerator.default_options
case options
when NilClass then @options
when String then @options.merge!(UnityModuleGenerator.grab_config(options))
when Hash then @options.merge!(options)
else raise 'If you specify arguments, it should be a filename or a hash of options'
end
# Create default file paths if none were provided
@options[:path_src] = here + '../src/' if @options[:path_src].nil?
@options[:path_inc] = @options[:path_src] if @options[:path_inc].nil?
@options[:path_tst] = here + '../test/' if @options[:path_tst].nil?
@options[:path_src] += '/' unless @options[:path_src][-1] == 47
@options[:path_inc] += '/' unless @options[:path_inc][-1] == 47
@options[:path_tst] += '/' unless @options[:path_tst][-1] == 47
# Built in patterns
@patterns = {
'src' => {
'' => { inc: [] }
},
'test' => {
'' => { inc: [] }
},
'dh' => {
'Driver' => { inc: [create_filename('%1$s', 'Hardware.h')] },
'Hardware' => { inc: [] }
},
'dih' => {
'Driver' => { inc: [create_filename('%1$s', 'Hardware.h'), create_filename('%1$s', 'Interrupt.h')] },
'Interrupt' => { inc: [create_filename('%1$s', 'Hardware.h')] },
'Hardware' => { inc: [] }
},
'mch' => {
'Model' => { inc: [] },
'Conductor' => { inc: [create_filename('%1$s', 'Model.h'), create_filename('%1$s', 'Hardware.h')] },
'Hardware' => { inc: [] }
},
'mvp' => {
'Model' => { inc: [] },
'Presenter' => { inc: [create_filename('%1$s', 'Model.h'), create_filename('%1$s', 'View.h')] },
'View' => { inc: [] }
}
}
end
############################
def self.default_options
{
pattern: 'src',
includes: {
src: [],
inc: [],
tst: []
},
update_svn: false,
boilerplates: {},
test_prefix: 'Test',
mock_prefix: 'Mock'
}
end
############################
def self.grab_config(config_file)
options = default_options
unless config_file.nil? || config_file.empty?
require 'yaml'
yaml_guts = YAML.load_file(config_file)
options.merge!(yaml_guts[:unity] || yaml_guts[:cmock])
raise "No :unity or :cmock section found in #{config_file}" unless options
end
options
end
############################
def files_to_operate_on(module_name, pattern = nil)
# strip any leading path information from the module name and save for later
subfolder = File.dirname(module_name)
module_name = File.basename(module_name)
# create triad definition
prefix = @options[:test_prefix] || 'Test'
triad = [{ ext: '.c', path: @options[:path_src], prefix: '', template: TEMPLATE_SRC, inc: :src, boilerplate: @options[:boilerplates][:src] },
{ ext: '.h', path: @options[:path_inc], prefix: '', template: TEMPLATE_INC, inc: :inc, boilerplate: @options[:boilerplates][:inc] },
{ ext: '.c', path: @options[:path_tst], prefix: prefix, template: TEMPLATE_TST, inc: :tst, boilerplate: @options[:boilerplates][:tst] }]
# prepare the pattern for use
pattern = (pattern || @options[:pattern] || 'src').downcase
patterns = @patterns[pattern]
raise "ERROR: The design pattern '#{pattern}' specified isn't one that I recognize!" if patterns.nil?
# single file patterns (currently just 'test') can reject the other parts of the triad
triad.select! { |v| v[:inc] == :tst } if pattern == 'test'
# Assemble the path/names of the files we need to work with.
files = []
triad.each do |cfg|
patterns.each_pair do |pattern_file, pattern_traits|
submodule_name = create_filename(module_name, pattern_file)
filename = cfg[:prefix] + submodule_name + cfg[:ext]
files << {
path: (Pathname.new("#{cfg[:path]}#{subfolder}") + filename).cleanpath,
name: submodule_name,
template: cfg[:template],
boilerplate: cfg[:boilerplate],
includes: case (cfg[:inc])
when :src then (@options[:includes][:src] || []) | (pattern_traits[:inc].map { |f| format(f, module_name) })
when :inc then (@options[:includes][:inc] || [])
when :tst then (@options[:includes][:tst] || []) | (pattern_traits[:inc].map { |f| format("#{@options[:mock_prefix]}#{f}", module_name) })
end
}
end
end
files
end
############################
def create_filename(part1, part2 = '')
if part2.empty?
case (@options[:naming])
when 'bumpy' then part1
when 'camel' then part1
when 'snake' then part1.downcase
when 'caps' then part1.upcase
else part1
end
else
case (@options[:naming])
when 'bumpy' then part1 + part2
when 'camel' then part1 + part2
when 'snake' then part1.downcase + '_' + part2.downcase
when 'caps' then part1.upcase + '_' + part2.upcase
else part1 + '_' + part2
end
end
end
############################
def generate(module_name, pattern = nil)
files = files_to_operate_on(module_name, pattern)
# Abort if all of the module files already exist
all_files_exist = true
files.each do |file|
all_files_exist = false unless File.exist?(file[:path])
end
raise "ERROR: File #{files[0][:name]} already exists. Exiting." if all_files_exist
# Create Source Modules
files.each_with_index do |file, _i|
# If this file already exists, don't overwrite it.
if File.exist?(file[:path])
puts "File #{file[:path]} already exists!"
next
end
# Create the path first if necessary.
FileUtils.mkdir_p(File.dirname(file[:path]), verbose: false)
File.open(file[:path], 'w') do |f|
f.write("#{file[:boilerplate]}\n" % [file[:name]]) unless file[:boilerplate].nil?
f.write(file[:template] % [file[:name],
file[:includes].map { |ff| "#include \"#{ff}\"\n" }.join,
file[:name].upcase])
end
if @options[:update_svn]
`svn add \"#{file[:path]}\"`
if $!.exitstatus.zero?
puts "File #{file[:path]} created and added to source control"
else
puts "File #{file[:path]} created but FAILED adding to source control!"
end
else
puts "File #{file[:path]} created"
end
end
puts 'Generate Complete'
end
############################
def destroy(module_name, pattern = nil)
files_to_operate_on(module_name, pattern).each do |filespec|
file = filespec[:path]
if File.exist?(file)
if @options[:update_svn]
`svn delete \"#{file}\" --force`
puts "File #{file} deleted and removed from source control"
else
FileUtils.remove(file)
puts "File #{file} deleted"
end
else
puts "File #{file} does not exist so cannot be removed."
end
end
puts 'Destroy Complete'
end
end
############################
# Handle As Command Line If Called That Way
if $0 == __FILE__
destroy = false
options = {}
module_name = nil
# Parse the command line parameters.
ARGV.each do |arg|
case arg
when /^-d/ then destroy = true
when /^-u/ then options[:update_svn] = true
when /^-p\"?(\w+)\"?/ then options[:pattern] = Regexp.last_match(1)
when /^-s\"?(.+)\"?/ then options[:path_src] = Regexp.last_match(1)
when /^-i\"?(.+)\"?/ then options[:path_inc] = Regexp.last_match(1)
when /^-t\"?(.+)\"?/ then options[:path_tst] = Regexp.last_match(1)
when /^-n\"?(.+)\"?/ then options[:naming] = Regexp.last_match(1)
when /^-y\"?(.+)\"?/ then options = UnityModuleGenerator.grab_config(Regexp.last_match(1))
when /^(\w+)/
raise "ERROR: You can't have more than one Module name specified!" unless module_name.nil?
module_name = arg
when /^-(h|-help)/
ARGV = [].freeze
else
raise "ERROR: Unknown option specified '#{arg}'"
end
end
unless ARGV[0]
puts ["\nGENERATE MODULE\n-------- ------",
"\nUsage: ruby generate_module [options] module_name",
" -i\"include\" sets the path to output headers to 'include' (DEFAULT ../src)",
" -s\"../src\" sets the path to output source to '../src' (DEFAULT ../src)",
" -t\"C:/test\" sets the path to output source to 'C:/test' (DEFAULT ../test)",
' -p"MCH" sets the output pattern to MCH.',
' dh - driver hardware.',
' dih - driver interrupt hardware.',
' mch - model conductor hardware.',
' mvp - model view presenter.',
' src - just a source module, header and test. (DEFAULT)',
' test - just a test file.',
' -d destroy module instead of creating it.',
' -n"camel" sets the file naming convention.',
' bumpy - BumpyCaseFilenames.',
' camel - camelCaseFilenames.',
' snake - snake_case_filenames.',
' caps - CAPS_CASE_FILENAMES.',
' -u update subversion too (requires subversion command line)',
' -y"my.yml" selects a different yaml config file for module generation',
''].join("\n")
exit
end
raise 'ERROR: You must have a Module name specified! (use option -h for help)' if module_name.nil?
if destroy
UnityModuleGenerator.new(options).destroy(module_name)
else
UnityModuleGenerator.new(options).generate(module_name)
end
end

View File

@ -0,0 +1,454 @@
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
File.expand_path(File.join(File.dirname(__FILE__), 'colour_prompt'))
class UnityTestRunnerGenerator
def initialize(options = nil)
@options = UnityTestRunnerGenerator.default_options
case options
when NilClass then @options
when String then @options.merge!(UnityTestRunnerGenerator.grab_config(options))
when Hash then @options.merge!(options)
else raise 'If you specify arguments, it should be a filename or a hash of options'
end
require "#{File.expand_path(File.dirname(__FILE__))}/type_sanitizer"
end
def self.default_options
{
includes: [],
defines: [],
plugins: [],
framework: :unity,
test_prefix: 'test|spec|should',
mock_prefix: 'Mock',
setup_name: 'setUp',
teardown_name: 'tearDown',
main_name: 'main', # set to :auto to automatically generate each time
main_export_decl: '',
cmdline_args: false,
use_param_tests: false
}
end
def self.grab_config(config_file)
options = default_options
unless config_file.nil? || config_file.empty?
require 'yaml'
yaml_guts = YAML.load_file(config_file)
options.merge!(yaml_guts[:unity] || yaml_guts[:cmock])
raise "No :unity or :cmock section found in #{config_file}" unless options
end
options
end
def run(input_file, output_file, options = nil)
@options.merge!(options) unless options.nil?
# pull required data from source file
source = File.read(input_file)
source = source.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
tests = find_tests(source)
headers = find_includes(source)
testfile_includes = (headers[:local] + headers[:system])
used_mocks = find_mocks(testfile_includes)
testfile_includes = (testfile_includes - used_mocks)
testfile_includes.delete_if { |inc| inc =~ /(unity|cmock)/ }
# build runner file
generate(input_file, output_file, tests, used_mocks, testfile_includes)
# determine which files were used to return them
all_files_used = [input_file, output_file]
all_files_used += testfile_includes.map { |filename| filename + '.c' } unless testfile_includes.empty?
all_files_used += @options[:includes] unless @options[:includes].empty?
all_files_used += headers[:linkonly] unless headers[:linkonly].empty?
all_files_used.uniq
end
def generate(input_file, output_file, tests, used_mocks, testfile_includes)
File.open(output_file, 'w') do |output|
create_header(output, used_mocks, testfile_includes)
create_externs(output, tests, used_mocks)
create_mock_management(output, used_mocks)
create_suite_setup(output)
create_suite_teardown(output)
create_reset(output, used_mocks)
create_main(output, input_file, tests, used_mocks)
end
return unless @options[:header_file] && !@options[:header_file].empty?
File.open(@options[:header_file], 'w') do |output|
create_h_file(output, @options[:header_file], tests, testfile_includes, used_mocks)
end
end
def find_tests(source)
tests_and_line_numbers = []
source_scrubbed = source.clone
source_scrubbed = source_scrubbed.gsub(/"[^"\n]*"/, '') # remove things in strings
source_scrubbed = source_scrubbed.gsub(/\/\/.*$/, '') # remove line comments
source_scrubbed = source_scrubbed.gsub(/\/\*.*?\*\//m, '') # remove block comments
lines = source_scrubbed.split(/(^\s*\#.*$) # Treat preprocessor directives as a logical line
| (;|\{|\}) /x) # Match ;, {, and } as end of lines
lines.each_with_index do |line, _index|
# find tests
next unless line =~ /^((?:\s*TEST_CASE\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]}).*)\s*\(\s*(.*)\s*\)/
arguments = Regexp.last_match(1)
name = Regexp.last_match(2)
call = Regexp.last_match(3)
params = Regexp.last_match(4)
args = nil
if @options[:use_param_tests] && !arguments.empty?
args = []
arguments.scan(/\s*TEST_CASE\s*\((.*)\)\s*$/) { |a| args << a[0] }
end
tests_and_line_numbers << { test: name, args: args, call: call, params: params, line_number: 0 }
end
tests_and_line_numbers.uniq! { |v| v[:test] }
# determine line numbers and create tests to run
source_lines = source.split("\n")
source_index = 0
tests_and_line_numbers.size.times do |i|
source_lines[source_index..-1].each_with_index do |line, index|
next unless line =~ /\s+#{tests_and_line_numbers[i][:test]}(?:\s|\()/
source_index += index
tests_and_line_numbers[i][:line_number] = source_index + 1
break
end
end
tests_and_line_numbers
end
def find_includes(source)
# remove comments (block and line, in three steps to ensure correct precedence)
source.gsub!(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks
source.gsub!(/\/\*.*?\*\//m, '') # remove block comments
source.gsub!(/\/\/.*$/, '') # remove line comments (all that remain)
# parse out includes
includes = {
local: source.scan(/^\s*#include\s+\"\s*(.+)\.[hH]\s*\"/).flatten,
system: source.scan(/^\s*#include\s+<\s*(.+)\s*>/).flatten.map { |inc| "<#{inc}>" },
linkonly: source.scan(/^TEST_FILE\(\s*\"\s*(.+)\.[cC]\w*\s*\"/).flatten
}
includes
end
def find_mocks(includes)
mock_headers = []
includes.each do |include_path|
include_file = File.basename(include_path)
mock_headers << include_path if include_file =~ /^#{@options[:mock_prefix]}/i
end
mock_headers
end
def create_header(output, mocks, testfile_includes = [])
output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */')
create_runtest(output, mocks)
output.puts("\n/*=======Automagically Detected Files To Include=====*/")
output.puts('#ifdef __WIN32__')
output.puts('#define UNITY_INCLUDE_SETUP_STUBS')
output.puts('#endif')
output.puts("#include \"#{@options[:framework]}.h\"")
output.puts('#include "cmock.h"') unless mocks.empty?
output.puts('#include <setjmp.h>')
output.puts('#include <stdio.h>')
if @options[:defines] && !@options[:defines].empty?
@options[:defines].each { |d| output.puts("#define #{d}") }
end
if @options[:header_file] && !@options[:header_file].empty?
output.puts("#include \"#{File.basename(@options[:header_file])}\"")
else
@options[:includes].flatten.uniq.compact.each do |inc|
output.puts("#include #{inc.include?('<') ? inc : "\"#{inc.gsub('.h', '')}.h\""}")
end
testfile_includes.each do |inc|
output.puts("#include #{inc.include?('<') ? inc : "\"#{inc.gsub('.h', '')}.h\""}")
end
end
mocks.each do |mock|
output.puts("#include \"#{mock.gsub('.h', '')}.h\"")
end
output.puts('#include "CException.h"') if @options[:plugins].include?(:cexception)
return unless @options[:enforce_strict_ordering]
output.puts('')
output.puts('int GlobalExpectCount;')
output.puts('int GlobalVerifyOrder;')
output.puts('char* GlobalOrderError;')
end
def create_externs(output, tests, _mocks)
output.puts("\n/*=======External Functions This Runner Calls=====*/")
output.puts("extern void #{@options[:setup_name]}(void);")
output.puts("extern void #{@options[:teardown_name]}(void);")
tests.each do |test|
output.puts("extern void #{test[:test]}(#{test[:call] || 'void'});")
end
output.puts('')
end
def create_mock_management(output, mock_headers)
return if mock_headers.empty?
output.puts("\n/*=======Mock Management=====*/")
output.puts('static void CMock_Init(void)')
output.puts('{')
if @options[:enforce_strict_ordering]
output.puts(' GlobalExpectCount = 0;')
output.puts(' GlobalVerifyOrder = 0;')
output.puts(' GlobalOrderError = NULL;')
end
mocks = mock_headers.map { |mock| File.basename(mock) }
mocks.each do |mock|
mock_clean = TypeSanitizer.sanitize_c_identifier(mock)
output.puts(" #{mock_clean}_Init();")
end
output.puts("}\n")
output.puts('static void CMock_Verify(void)')
output.puts('{')
mocks.each do |mock|
mock_clean = TypeSanitizer.sanitize_c_identifier(mock)
output.puts(" #{mock_clean}_Verify();")
end
output.puts("}\n")
output.puts('static void CMock_Destroy(void)')
output.puts('{')
mocks.each do |mock|
mock_clean = TypeSanitizer.sanitize_c_identifier(mock)
output.puts(" #{mock_clean}_Destroy();")
end
output.puts("}\n")
end
def create_suite_setup(output)
output.puts("\n/*=======Suite Setup=====*/")
output.puts('static void suite_setup(void)')
output.puts('{')
if @options[:suite_setup].nil?
# New style, call suiteSetUp() if we can use weak symbols
output.puts('#if defined(UNITY_WEAK_ATTRIBUTE) || defined(UNITY_WEAK_PRAGMA)')
output.puts(' suiteSetUp();')
output.puts('#endif')
else
# Old style, C code embedded in the :suite_setup option
output.puts(@options[:suite_setup])
end
output.puts('}')
end
def create_suite_teardown(output)
output.puts("\n/*=======Suite Teardown=====*/")
output.puts('static int suite_teardown(int num_failures)')
output.puts('{')
if @options[:suite_teardown].nil?
# New style, call suiteTearDown() if we can use weak symbols
output.puts('#if defined(UNITY_WEAK_ATTRIBUTE) || defined(UNITY_WEAK_PRAGMA)')
output.puts(' return suiteTearDown(num_failures);')
output.puts('#else')
output.puts(' return num_failures;')
output.puts('#endif')
else
# Old style, C code embedded in the :suite_teardown option
output.puts(@options[:suite_teardown])
end
output.puts('}')
end
def create_runtest(output, used_mocks)
cexception = @options[:plugins].include? :cexception
va_args1 = @options[:use_param_tests] ? ', ...' : ''
va_args2 = @options[:use_param_tests] ? '__VA_ARGS__' : ''
output.puts("\n/*=======Test Runner Used To Run Each Test Below=====*/")
output.puts('#define RUN_TEST_NO_ARGS') if @options[:use_param_tests]
output.puts("#define RUN_TEST(TestFunc, TestLineNum#{va_args1}) \\")
output.puts('{ \\')
output.puts(" Unity.CurrentTestName = #TestFunc#{va_args2.empty? ? '' : " \"(\" ##{va_args2} \")\""}; \\")
output.puts(' Unity.CurrentTestLineNumber = TestLineNum; \\')
output.puts(' if (UnityTestMatches()) { \\') if @options[:cmdline_args]
output.puts(' Unity.NumberOfTests++; \\')
output.puts(' CMock_Init(); \\') unless used_mocks.empty?
output.puts(' UNITY_CLR_DETAILS(); \\') unless used_mocks.empty?
output.puts(' if (TEST_PROTECT()) \\')
output.puts(' { \\')
output.puts(' CEXCEPTION_T e; \\') if cexception
output.puts(' Try { \\') if cexception
output.puts(" #{@options[:setup_name]}(); \\")
output.puts(" TestFunc(#{va_args2}); \\")
output.puts(' } Catch(e) { TEST_ASSERT_EQUAL_HEX32_MESSAGE(CEXCEPTION_NONE, e, "Unhandled Exception!"); } \\') if cexception
output.puts(' } \\')
output.puts(' if (TEST_PROTECT()) \\')
output.puts(' { \\')
output.puts(" #{@options[:teardown_name]}(); \\")
output.puts(' CMock_Verify(); \\') unless used_mocks.empty?
output.puts(' } \\')
output.puts(' CMock_Destroy(); \\') unless used_mocks.empty?
output.puts(' UnityConcludeTest(); \\')
output.puts(' } \\') if @options[:cmdline_args]
output.puts("}\n")
end
def create_reset(output, used_mocks)
output.puts("\n/*=======Test Reset Option=====*/")
output.puts('void resetTest(void);')
output.puts('void resetTest(void)')
output.puts('{')
output.puts(' CMock_Verify();') unless used_mocks.empty?
output.puts(' CMock_Destroy();') unless used_mocks.empty?
output.puts(" #{@options[:teardown_name]}();")
output.puts(' CMock_Init();') unless used_mocks.empty?
output.puts(" #{@options[:setup_name]}();")
output.puts('}')
end
def create_main(output, filename, tests, used_mocks)
output.puts("\n\n/*=======MAIN=====*/")
main_name = @options[:main_name].to_sym == :auto ? "main_#{filename.gsub('.c', '')}" : (@options[:main_name]).to_s
if @options[:cmdline_args]
if main_name != 'main'
output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv);")
end
output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv)")
output.puts('{')
output.puts(' int parse_status = UnityParseOptions(argc, argv);')
output.puts(' if (parse_status != 0)')
output.puts(' {')
output.puts(' if (parse_status < 0)')
output.puts(' {')
output.puts(" UnityPrint(\"#{filename.gsub('.c', '')}.\");")
output.puts(' UNITY_PRINT_EOL();')
if @options[:use_param_tests]
tests.each do |test|
if test[:args].nil? || test[:args].empty?
output.puts(" UnityPrint(\" #{test[:test]}(RUN_TEST_NO_ARGS)\");")
output.puts(' UNITY_PRINT_EOL();')
else
test[:args].each do |args|
output.puts(" UnityPrint(\" #{test[:test]}(#{args})\");")
output.puts(' UNITY_PRINT_EOL();')
end
end
end
else
tests.each { |test| output.puts(" UnityPrint(\" #{test[:test]}\");\n UNITY_PRINT_EOL();") }
end
output.puts(' return 0;')
output.puts(' }')
output.puts(' return parse_status;')
output.puts(' }')
else
if main_name != 'main'
output.puts("#{@options[:main_export_decl]} int #{main_name}(void);")
end
output.puts("int #{main_name}(void)")
output.puts('{')
end
output.puts(' suite_setup();')
output.puts(" UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");")
if @options[:use_param_tests]
tests.each do |test|
if test[:args].nil? || test[:args].empty?
output.puts(" RUN_TEST(#{test[:test]}, #{test[:line_number]}, RUN_TEST_NO_ARGS);")
else
test[:args].each { |args| output.puts(" RUN_TEST(#{test[:test]}, #{test[:line_number]}, #{args});") }
end
end
else
tests.each { |test| output.puts(" RUN_TEST(#{test[:test]}, #{test[:line_number]});") }
end
output.puts
output.puts(' CMock_Guts_MemFreeFinal();') unless used_mocks.empty?
output.puts(" return suite_teardown(UnityEnd());")
output.puts('}')
end
def create_h_file(output, filename, tests, testfile_includes, used_mocks)
filename = File.basename(filename).gsub(/[-\/\\\.\,\s]/, '_').upcase
output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */')
output.puts("#ifndef _#{filename}")
output.puts("#define _#{filename}\n\n")
output.puts("#include \"#{@options[:framework]}.h\"")
output.puts('#include "cmock.h"') unless used_mocks.empty?
@options[:includes].flatten.uniq.compact.each do |inc|
output.puts("#include #{inc.include?('<') ? inc : "\"#{inc.gsub('.h', '')}.h\""}")
end
testfile_includes.each do |inc|
output.puts("#include #{inc.include?('<') ? inc : "\"#{inc.gsub('.h', '')}.h\""}")
end
output.puts "\n"
tests.each do |test|
if test[:params].nil? || test[:params].empty?
output.puts("void #{test[:test]}(void);")
else
output.puts("void #{test[:test]}(#{test[:params]});")
end
end
output.puts("#endif\n\n")
end
end
if $0 == __FILE__
options = { includes: [] }
# parse out all the options first (these will all be removed as we go)
ARGV.reject! do |arg|
case arg
when '-cexception'
options[:plugins] = [:cexception]
true
when /\.*\.ya?ml/
options = UnityTestRunnerGenerator.grab_config(arg)
true
when /--(\w+)=\"?(.*)\"?/
options[Regexp.last_match(1).to_sym] = Regexp.last_match(2)
true
when /\.*\.h/
options[:includes] << arg
true
else false
end
end
# make sure there is at least one parameter left (the input file)
unless ARGV[0]
puts ["\nusage: ruby #{__FILE__} (files) (options) input_test_file (output)",
"\n input_test_file - this is the C file you want to create a runner for",
' output - this is the name of the runner file to generate',
' defaults to (input_test_file)_Runner',
' files:',
' *.yml / *.yaml - loads configuration from here in :unity or :cmock',
' *.h - header files are added as #includes in runner',
' options:',
' -cexception - include cexception support',
' --setup_name="" - redefine setUp func name to something else',
' --teardown_name="" - redefine tearDown func name to something else',
' --main_name="" - redefine main func name to something else',
' --test_prefix="" - redefine test prefix from default test|spec|should',
' --suite_setup="" - code to execute for setup of entire suite',
' --suite_teardown="" - code to execute for teardown of entire suite',
' --use_param_tests=1 - enable parameterized tests (disabled by default)',
' --header_file="" - path/name of test header file to generate too'].join("\n")
exit 1
end
# create the default test runner name if not specified
ARGV[1] = ARGV[0].gsub('.c', '_Runner.c') unless ARGV[1]
UnityTestRunnerGenerator.new(options).run(ARGV[0], ARGV[1])
end

View File

@ -0,0 +1,220 @@
#============================================================
# Author: John Theofanopoulos
# A simple parser. Takes the output files generated during the build process and
# extracts information relating to the tests.
#
# Notes:
# To capture an output file under VS builds use the following:
# devenv [build instructions] > Output.txt & type Output.txt
#
# To capture an output file under GCC/Linux builds use the following:
# make | tee Output.txt
#
# To use this parser use the following command
# ruby parseOutput.rb [options] [file]
# options: -xml : produce a JUnit compatible XML file
# file : file to scan for results
#============================================================
class ParseOutput
def initialize
@test_flag = false
@xml_out = false
@array_list = false
@total_tests = false
@class_index = false
end
# Set the flag to indicate if there will be an XML output file or not
def set_xml_output
@xml_out = true
end
# if write our output to XML
def write_xml_output
output = File.open('report.xml', 'w')
output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
@array_list.each do |item|
output << item << "\n"
end
output << "</testsuite>\n"
end
# This function will try and determine when the suite is changed. This is
# is the name that gets added to the classname parameter.
def test_suite_verify(test_suite_name)
return if @test_flag
@test_flag = true
# Split the path name
test_name = test_suite_name.split('/')
# Remove the extension
base_name = test_name[test_name.size - 1].split('.')
@test_suite = 'test.' + base_name[0]
printf "New Test: %s\n", @test_suite
end
# Test was flagged as having passed so format the output
def test_passed(array)
last_item = array.length - 1
test_name = array[last_item - 1]
test_suite_verify(array[@class_name])
printf "%-40s PASS\n", test_name
return unless @xml_out
@array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '"/>'
end
# Test was flagged as having passed so format the output.
# This is using the Unity fixture output and not the original Unity output.
def test_passed_unity_fixture(array)
test_suite = array[0].sub('TEST(', '')
test_suite = test_suite.sub(',', '')
test_name = array[1].sub(')', '')
return unless @xml_out
@array_list.push ' <testcase classname="' + test_suite + '" name="' + test_name + '"/>'
end
# Test was flagged as being ingored so format the output
def test_ignored(array)
last_item = array.length - 1
test_name = array[last_item - 2]
reason = array[last_item].chomp
test_suite_verify(array[@class_name])
printf "%-40s IGNORED\n", test_name
if test_name.start_with? 'TEST('
array2 = test_name.split(' ')
@test_suite = array2[0].sub('TEST(', '')
@test_suite = @test_suite.sub(',', '')
test_name = array2[1].sub(')', '')
end
return unless @xml_out
@array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">'
@array_list.push ' <skipped type="TEST IGNORED"> ' + reason + ' </skipped>'
@array_list.push ' </testcase>'
end
# Test was flagged as having failed so format the line
def test_failed(array)
last_item = array.length - 1
test_name = array[last_item - 2]
reason = array[last_item].chomp + ' at line: ' + array[last_item - 3]
test_suite_verify(array[@class_name])
printf "%-40s FAILED\n", test_name
if test_name.start_with? 'TEST('
array2 = test_name.split(' ')
@test_suite = array2[0].sub('TEST(', '')
@test_suite = @test_suite.sub(',', '')
test_name = array2[1].sub(')', '')
end
return unless @xml_out
@array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">'
@array_list.push ' <failure type="ASSERT FAILED"> ' + reason + ' </failure>'
@array_list.push ' </testcase>'
end
# Figure out what OS we are running on. For now we are assuming if it's not Windows it must
# be Unix based.
def detect_os
os = RUBY_PLATFORM.split('-')
@class_name = if os.size == 2
if os[1] == 'mingw32'
1
else
0
end
else
0
end
end
# Main function used to parse the file that was captured.
def process(name)
@test_flag = false
@array_list = []
detect_os
puts 'Parsing file: ' + name
test_pass = 0
test_fail = 0
test_ignore = 0
puts ''
puts '=================== RESULTS ====================='
puts ''
File.open(name).each do |line|
# Typical test lines look like this:
# <path>/<test_file>.c:36:test_tc1000_opsys:FAIL: Expected 1 Was 0
# <path>/<test_file>.c:112:test_tc5004_initCanChannel:IGNORE: Not Yet Implemented
# <path>/<test_file>.c:115:test_tc5100_initCanVoidPtrs:PASS
#
# where path is different on Unix vs Windows devices (Windows leads with a drive letter)
line_array = line.split(':')
# If we were able to split the line then we can look to see if any of our target words
# were found. Case is important.
if (line_array.size >= 4) || (line.start_with? 'TEST(')
# Determine if this test passed
if line.include? ':PASS'
test_passed(line_array)
test_pass += 1
elsif line.include? ':FAIL:'
test_failed(line_array)
test_fail += 1
elsif line.include? ':IGNORE:'
test_ignored(line_array)
test_ignore += 1
elsif line.start_with? 'TEST('
if line.include? ' PASS'
line_array = line.split(' ')
test_passed_unity_fixture(line_array)
test_pass += 1
end
# If none of the keywords are found there are no more tests for this suite so clear
# the test flag
else
@test_flag = false
end
else
@test_flag = false
end
end
puts ''
puts '=================== SUMMARY ====================='
puts ''
puts 'Tests Passed : ' + test_pass.to_s
puts 'Tests Failed : ' + test_fail.to_s
puts 'Tests Ignored : ' + test_ignore.to_s
@total_tests = test_pass + test_fail + test_ignore
return unless @xml_out
heading = '<testsuite tests="' + @total_tests.to_s + '" failures="' + test_fail.to_s + '"' + ' skips="' + test_ignore.to_s + '">'
@array_list.insert(0, heading)
write_xml_output
end
end
# If the command line has no values in, used a default value of Output.txt
parse_my_file = ParseOutput.new
if ARGV.size >= 1
ARGV.each do |a|
if a == '-xml'
parse_my_file.set_xml_output
else
parse_my_file.process(a)
break
end
end
end

View File

@ -0,0 +1,252 @@
#!/usr/bin/ruby
#
# unity_to_junit.rb
#
require 'fileutils'
require 'optparse'
require 'ostruct'
require 'set'
require 'pp'
VERSION = 1.0
class ArgvParser
#
# Return a structure describing the options.
#
def self.parse(args)
# The options specified on the command line will be collected in *options*.
# We set default values here.
options = OpenStruct.new
options.results_dir = '.'
options.root_path = '.'
options.out_file = 'results.xml'
opts = OptionParser.new do |o|
o.banner = 'Usage: unity_to_junit.rb [options]'
o.separator ''
o.separator 'Specific options:'
o.on('-r', '--results <dir>', 'Look for Unity Results files here.') do |results|
# puts "results #{results}"
options.results_dir = results
end
o.on('-p', '--root_path <path>', 'Prepend this path to files in results.') do |root_path|
options.root_path = root_path
end
o.on('-o', '--output <filename>', 'XML file to generate.') do |out_file|
# puts "out_file: #{out_file}"
options.out_file = out_file
end
o.separator ''
o.separator 'Common options:'
# No argument, shows at tail. This will print an options summary.
o.on_tail('-h', '--help', 'Show this message') do
puts o
exit
end
# Another typical switch to print the version.
o.on_tail('--version', 'Show version') do
puts "unity_to_junit.rb version #{VERSION}"
exit
end
end
opts.parse!(args)
options
end # parse()
end # class OptparseExample
class UnityToJUnit
include FileUtils::Verbose
attr_reader :report, :total_tests, :failures, :ignored
attr_writer :targets, :root, :out_file
def initialize
@report = ''
@unit_name = ''
end
def run
# Clean up result file names
results = @targets.map { |target| target.tr('\\', '/') }
# puts "Output File: #{@out_file}"
f = File.new(@out_file, 'w')
write_xml_header(f)
write_suites_header(f)
results.each do |result_file|
lines = File.readlines(result_file).map(&:chomp)
raise "Empty test result file: #{result_file}" if lines.empty?
result_output = get_details(result_file, lines)
tests, failures, ignored = parse_test_summary(lines)
result_output[:counts][:total] = tests
result_output[:counts][:failed] = failures
result_output[:counts][:ignored] = ignored
result_output[:counts][:passed] = (result_output[:counts][:total] - result_output[:counts][:failed] - result_output[:counts][:ignored])
# use line[0] from the test output to get the test_file path and name
test_file_str = lines[0].tr('\\', '/')
test_file_str = test_file_str.split(':')
test_file = if test_file_str.length < 2
result_file
else
test_file_str[0] + ':' + test_file_str[1]
end
result_output[:source][:path] = File.dirname(test_file)
result_output[:source][:file] = File.basename(test_file)
# save result_output
@unit_name = File.basename(test_file, '.*')
write_suite_header(result_output[:counts], f)
write_failures(result_output, f)
write_tests(result_output, f)
write_ignored(result_output, f)
write_suite_footer(f)
end
write_suites_footer(f)
f.close
end
def usage(err_msg = nil)
puts "\nERROR: "
puts err_msg if err_msg
puts 'Usage: unity_to_junit.rb [options]'
puts ''
puts 'Specific options:'
puts ' -r, --results <dir> Look for Unity Results files here.'
puts ' -p, --root_path <path> Prepend this path to files in results.'
puts ' -o, --output <filename> XML file to generate.'
puts ''
puts 'Common options:'
puts ' -h, --help Show this message'
puts ' --version Show version'
exit 1
end
protected
def get_details(_result_file, lines)
results = results_structure
lines.each do |line|
line = line.tr('\\', '/')
_src_file, src_line, test_name, status, msg = line.split(/:/)
case status
when 'IGNORE' then results[:ignores] << { test: test_name, line: src_line, message: msg }
when 'FAIL' then results[:failures] << { test: test_name, line: src_line, message: msg }
when 'PASS' then results[:successes] << { test: test_name, line: src_line, message: msg }
end
end
results
end
def parse_test_summary(summary)
raise "Couldn't parse test results: #{summary}" unless summary.find { |v| v =~ /(\d+) Tests (\d+) Failures (\d+) Ignored/ }
[Regexp.last_match(1).to_i, Regexp.last_match(2).to_i, Regexp.last_match(3).to_i]
end
def here
File.expand_path(File.dirname(__FILE__))
end
private
def results_structure
{
source: { path: '', file: '' },
successes: [],
failures: [],
ignores: [],
counts: { total: 0, passed: 0, failed: 0, ignored: 0 },
stdout: []
}
end
def write_xml_header(stream)
stream.puts "<?xml version='1.0' encoding='utf-8' ?>"
end
def write_suites_header(stream)
stream.puts '<testsuites>'
end
def write_suite_header(counts, stream)
stream.puts "\t<testsuite errors=\"0\" skipped=\"#{counts[:ignored]}\" failures=\"#{counts[:failed]}\" tests=\"#{counts[:total]}\" name=\"unity\">"
end
def write_failures(results, stream)
result = results[:failures]
result.each do |item|
filename = File.join(results[:source][:path], File.basename(results[:source][:file], '.*'))
stream.puts "\t\t<testcase classname=\"#{@unit_name}\" name=\"#{item[:test]}\" time=\"0\">"
stream.puts "\t\t\t<failure message=\"#{item[:message]}\" type=\"Assertion\"/>"
stream.puts "\t\t\t<system-err>&#xD;[File] #{filename}&#xD;[Line] #{item[:line]}&#xD;</system-err>"
stream.puts "\t\t</testcase>"
end
end
def write_tests(results, stream)
result = results[:successes]
result.each do |item|
stream.puts "\t\t<testcase classname=\"#{@unit_name}\" name=\"#{item[:test]}\" time=\"0\" />"
end
end
def write_ignored(results, stream)
result = results[:ignores]
result.each do |item|
filename = File.join(results[:source][:path], File.basename(results[:source][:file], '.*'))
puts "Writing ignored tests for test harness: #{filename}"
stream.puts "\t\t<testcase classname=\"#{@unit_name}\" name=\"#{item[:test]}\" time=\"0\">"
stream.puts "\t\t\t<skipped message=\"#{item[:message]}\" type=\"Assertion\"/>"
stream.puts "\t\t\t<system-err>&#xD;[File] #{filename}&#xD;[Line] #{item[:line]}&#xD;</system-err>"
stream.puts "\t\t</testcase>"
end
end
def write_suite_footer(stream)
stream.puts "\t</testsuite>"
end
def write_suites_footer(stream)
stream.puts '</testsuites>'
end
end # UnityToJUnit
if __FILE__ == $0
# parse out the command options
options = ArgvParser.parse(ARGV)
# create an instance to work with
utj = UnityToJUnit.new
begin
# look in the specified or current directory for result files
targets = "#{options.results_dir.tr('\\', '/')}**/*.test*"
results = Dir[targets]
raise "No *.testpass, *.testfail, or *.testresults files found in '#{targets}'" if results.empty?
utj.targets = results
# set the root path
utj.root = options.root_path
# set the output XML file name
# puts "Output File from options: #{options.out_file}"
utj.out_file = options.out_file
# run the summarizer
puts utj.run
rescue StandardError => e
utj.usage e.message
end
end

View File

@ -0,0 +1,25 @@
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
require'yaml'
module RakefileHelpers
class TestFileFilter
def initialize(all_files = false)
@all_files = all_files
return false unless @all_files
return false unless File.exist?('test_file_filter.yml')
filters = YAML.load_file('test_file_filter.yml')
@all_files = filters[:all_files]
@only_files = filters[:only_files]
@exclude_files = filters[:exclude_files]
end
attr_accessor :all_files, :only_files, :exclude_files
end
end

View File

@ -0,0 +1,6 @@
module TypeSanitizer
def self.sanitize_c_identifier(unsanitized)
# convert filename to valid C identifier by replacing invalid chars with '_'
unsanitized.gsub(/[-\/\\\.\,\s]/, '_')
end
end

View File

@ -0,0 +1,139 @@
#! python3
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2015 Alexander Mueller / XelaRellum@web.de
# [Released under MIT License. Please refer to license.txt for details]
# Based on the ruby script by Mike Karlesky, Mark VanderVoord, Greg Williams
# ==========================================
import sys
import os
import re
from glob import glob
class UnityTestSummary:
def __init__(self):
self.report = ''
self.total_tests = 0
self.failures = 0
self.ignored = 0
def run(self):
# Clean up result file names
results = []
for target in self.targets:
results.append(target.replace('\\', '/'))
# Dig through each result file, looking for details on pass/fail:
failure_output = []
ignore_output = []
for result_file in results:
lines = list(map(lambda line: line.rstrip(), open(result_file, "r").read().split('\n')))
if len(lines) == 0:
raise Exception("Empty test result file: %s" % result_file)
details = self.get_details(result_file, lines)
failures = details['failures']
ignores = details['ignores']
if len(failures) > 0: failure_output.append('\n'.join(failures))
if len(ignores) > 0: ignore_output.append('n'.join(ignores))
tests,failures,ignored = self.parse_test_summary('\n'.join(lines))
self.total_tests += tests
self.failures += failures
self.ignored += ignored
if self.ignored > 0:
self.report += "\n"
self.report += "--------------------------\n"
self.report += "UNITY IGNORED TEST SUMMARY\n"
self.report += "--------------------------\n"
self.report += "\n".join(ignore_output)
if self.failures > 0:
self.report += "\n"
self.report += "--------------------------\n"
self.report += "UNITY FAILED TEST SUMMARY\n"
self.report += "--------------------------\n"
self.report += '\n'.join(failure_output)
self.report += "\n"
self.report += "--------------------------\n"
self.report += "OVERALL UNITY TEST SUMMARY\n"
self.report += "--------------------------\n"
self.report += "{total_tests} TOTAL TESTS {failures} TOTAL FAILURES {ignored} IGNORED\n".format(total_tests = self.total_tests, failures=self.failures, ignored=self.ignored)
self.report += "\n"
return self.report
def set_targets(self, target_array):
self.targets = target_array
def set_root_path(self, path):
self.root = path
def usage(self, err_msg=None):
print("\nERROR: ")
if err_msg:
print(err_msg)
print("\nUsage: unity_test_summary.py result_file_directory/ root_path/")
print(" result_file_directory - The location of your results files.")
print(" Defaults to current directory if not specified.")
print(" Should end in / if specified.")
print(" root_path - Helpful for producing more verbose output if using relative paths.")
sys.exit(1)
def get_details(self, result_file, lines):
results = { 'failures': [], 'ignores': [], 'successes': [] }
for line in lines:
parts = line.split(':')
if len(parts) == 5:
src_file,src_line,test_name,status,msg = parts
elif len(parts) == 4:
src_file,src_line,test_name,status = parts
msg = ''
else:
continue
if len(self.root) > 0:
line_out = "%s%s" % (self.root, line)
else:
line_out = line
if status == 'IGNORE':
results['ignores'].append(line_out)
elif status == 'FAIL':
results['failures'].append(line_out)
elif status == 'PASS':
results['successes'].append(line_out)
return results
def parse_test_summary(self, summary):
m = re.search(r"([0-9]+) Tests ([0-9]+) Failures ([0-9]+) Ignored", summary)
if not m:
raise Exception("Couldn't parse test results: %s" % summary)
return int(m.group(1)), int(m.group(2)), int(m.group(3))
if __name__ == '__main__':
uts = UnityTestSummary()
try:
#look in the specified or current directory for result files
if len(sys.argv) > 1:
targets_dir = sys.argv[1]
else:
targets_dir = './'
targets = list(map(lambda x: x.replace('\\', '/'), glob(targets_dir + '*.test*')))
if len(targets) == 0:
raise Exception("No *.testpass or *.testfail files found in '%s'" % targets_dir)
uts.set_targets(targets)
#set the root path
if len(sys.argv) > 2:
root_path = sys.argv[2]
else:
root_path = os.path.split(__file__)[0]
uts.set_root_path(root_path)
#run the summarizer
print(uts.run())
except Exception as e:
uts.usage(e)

View File

@ -0,0 +1,136 @@
# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
# !/usr/bin/ruby
#
# unity_test_summary.rb
#
require 'fileutils'
require 'set'
class UnityTestSummary
include FileUtils::Verbose
attr_reader :report, :total_tests, :failures, :ignored
attr_writer :targets, :root
def initialize(_opts = {})
@report = ''
@total_tests = 0
@failures = 0
@ignored = 0
end
def run
# Clean up result file names
results = @targets.map { |target| target.tr('\\', '/') }
# Dig through each result file, looking for details on pass/fail:
failure_output = []
ignore_output = []
results.each do |result_file|
lines = File.readlines(result_file).map(&:chomp)
raise "Empty test result file: #{result_file}" if lines.empty?
output = get_details(result_file, lines)
failure_output << output[:failures] unless output[:failures].empty?
ignore_output << output[:ignores] unless output[:ignores].empty?
tests, failures, ignored = parse_test_summary(lines)
@total_tests += tests
@failures += failures
@ignored += ignored
end
if @ignored > 0
@report += "\n"
@report += "--------------------------\n"
@report += "UNITY IGNORED TEST SUMMARY\n"
@report += "--------------------------\n"
@report += ignore_output.flatten.join("\n")
end
if @failures > 0
@report += "\n"
@report += "--------------------------\n"
@report += "UNITY FAILED TEST SUMMARY\n"
@report += "--------------------------\n"
@report += failure_output.flatten.join("\n")
end
@report += "\n"
@report += "--------------------------\n"
@report += "OVERALL UNITY TEST SUMMARY\n"
@report += "--------------------------\n"
@report += "#{@total_tests} TOTAL TESTS #{@failures} TOTAL FAILURES #{@ignored} IGNORED\n"
@report += "\n"
end
def usage(err_msg = nil)
puts "\nERROR: "
puts err_msg if err_msg
puts "\nUsage: unity_test_summary.rb result_file_directory/ root_path/"
puts ' result_file_directory - The location of your results files.'
puts ' Defaults to current directory if not specified.'
puts ' Should end in / if specified.'
puts ' root_path - Helpful for producing more verbose output if using relative paths.'
exit 1
end
protected
def get_details(_result_file, lines)
results = { failures: [], ignores: [], successes: [] }
lines.each do |line|
_src_file, _src_line, _test_name, status, _msg = line.split(/:/)
line_out = (@root && (@root != 0) ? "#{@root}#{line}" : line).gsub(/\//, '\\')
case status
when 'IGNORE' then results[:ignores] << line_out
when 'FAIL' then results[:failures] << line_out
when 'PASS' then results[:successes] << line_out
end
end
results
end
def parse_test_summary(summary)
raise "Couldn't parse test results: #{summary}" unless summary.find { |v| v =~ /(\d+) Tests (\d+) Failures (\d+) Ignored/ }
[Regexp.last_match(1).to_i, Regexp.last_match(2).to_i, Regexp.last_match(3).to_i]
end
def here
File.expand_path(File.dirname(__FILE__))
end
end
if $0 == __FILE__
# parse out the command options
opts, args = ARGV.partition { |v| v =~ /^--\w+/ }
opts.map! { |v| v[2..-1].to_sym }
# create an instance to work with
uts = UnityTestSummary.new(opts)
begin
# look in the specified or current directory for result files
args[0] ||= './'
targets = "#{ARGV[0].tr('\\', '/')}**/*.test*"
results = Dir[targets]
raise "No *.testpass, *.testfail, or *.testresults files found in '#{targets}'" if results.empty?
uts.targets = results
# set the root path
args[1] ||= Dir.pwd + '/'
uts.root = ARGV[1]
# run the summarizer
puts uts.run
rescue StandardError => e
uts.usage e.message
end
end

View File

@ -0,0 +1,146 @@
import sys
import os
from glob import glob
from pyparsing import *
from junit_xml import TestSuite, TestCase
class UnityTestSummary:
def __init__(self):
self.report = ''
self.total_tests = 0
self.failures = 0
self.ignored = 0
self.targets = 0
self.root = None
self.test_suites = dict()
def run(self):
# Clean up result file names
results = []
for target in self.targets:
results.append(target.replace('\\', '/'))
# Dig through each result file, looking for details on pass/fail:
for result_file in results:
lines = list(map(lambda line: line.rstrip(), open(result_file, "r").read().split('\n')))
if len(lines) == 0:
raise Exception("Empty test result file: %s" % result_file)
# define an expression for your file reference
entry_one = Combine(
oneOf(list(alphas)) + ':/' +
Word(alphanums + '_-./'))
entry_two = Word(printables + ' ', excludeChars=':')
entry = entry_one | entry_two
delimiter = Literal(':').suppress()
tc_result_line = Group(entry.setResultsName('tc_file_name') + delimiter + entry.setResultsName(
'tc_line_nr') + delimiter + entry.setResultsName('tc_name') + delimiter + entry.setResultsName(
'tc_status') + Optional(
delimiter + entry.setResultsName('tc_msg'))).setResultsName("tc_line")
eol = LineEnd().suppress()
sol = LineStart().suppress()
blank_line = sol + eol
tc_summary_line = Group(Word(nums).setResultsName("num_of_tests") + "Tests" + Word(nums).setResultsName(
"num_of_fail") + "Failures" + Word(nums).setResultsName("num_of_ignore") + "Ignored").setResultsName(
"tc_summary")
tc_end_line = Or(Literal("FAIL"), Literal('Ok')).setResultsName("tc_result")
# run it and see...
pp1 = tc_result_line | Optional(tc_summary_line | tc_end_line)
pp1.ignore(blank_line | OneOrMore("-"))
result = list()
for l in lines:
result.append((pp1.parseString(l)).asDict())
# delete empty results
result = filter(None, result)
tc_list = list()
for r in result:
if 'tc_line' in r:
tmp_tc_line = r['tc_line']
# get only the file name which will be used as the classname
file_name = tmp_tc_line['tc_file_name'].split('\\').pop().split('/').pop().rsplit('.', 1)[0]
tmp_tc = TestCase(name=tmp_tc_line['tc_name'], classname=file_name)
if 'tc_status' in tmp_tc_line:
if str(tmp_tc_line['tc_status']) == 'IGNORE':
if 'tc_msg' in tmp_tc_line:
tmp_tc.add_skipped_info(message=tmp_tc_line['tc_msg'],
output=r'[File]={0}, [Line]={1}'.format(
tmp_tc_line['tc_file_name'], tmp_tc_line['tc_line_nr']))
else:
tmp_tc.add_skipped_info(message=" ")
elif str(tmp_tc_line['tc_status']) == 'FAIL':
if 'tc_msg' in tmp_tc_line:
tmp_tc.add_failure_info(message=tmp_tc_line['tc_msg'],
output=r'[File]={0}, [Line]={1}'.format(
tmp_tc_line['tc_file_name'], tmp_tc_line['tc_line_nr']))
else:
tmp_tc.add_failure_info(message=" ")
tc_list.append((str(result_file), tmp_tc))
for k, v in tc_list:
try:
self.test_suites[k].append(v)
except KeyError:
self.test_suites[k] = [v]
ts = []
for suite_name in self.test_suites:
ts.append(TestSuite(suite_name, self.test_suites[suite_name]))
with open('result.xml', 'w') as f:
TestSuite.to_file(f, ts, prettyprint='True', encoding='utf-8')
return self.report
def set_targets(self, target_array):
self.targets = target_array
def set_root_path(self, path):
self.root = path
@staticmethod
def usage(err_msg=None):
print("\nERROR: ")
if err_msg:
print(err_msg)
print("\nUsage: unity_test_summary.py result_file_directory/ root_path/")
print(" result_file_directory - The location of your results files.")
print(" Defaults to current directory if not specified.")
print(" Should end in / if specified.")
print(" root_path - Helpful for producing more verbose output if using relative paths.")
sys.exit(1)
if __name__ == '__main__':
uts = UnityTestSummary()
try:
# look in the specified or current directory for result files
if len(sys.argv) > 1:
targets_dir = sys.argv[1]
else:
targets_dir = './'
targets = list(map(lambda x: x.replace('\\', '/'), glob(targets_dir + '*.test*')))
if len(targets) == 0:
raise Exception("No *.testpass or *.testfail files found in '%s'" % targets_dir)
uts.set_targets(targets)
# set the root path
if len(sys.argv) > 2:
root_path = sys.argv[2]
else:
root_path = os.path.split(__file__)[0]
uts.set_root_path(root_path)
# run the summarizer
print(uts.run())
except Exception as e:
UnityTestSummary.usage(e)