mirror of
https://github.com/ThrowTheSwitch/Unity.git
synced 2025-05-29 22:46:27 +08:00

The include must be in the first line, else you may expect some issues. Some autoformat tools could sort the includes alphabetically and could break the test.
308 lines
11 KiB
Ruby
308 lines
11 KiB
Ruby
# ==========================================
|
|
# 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)
|
|
@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] = "#{__dir__}/../src/" if @options[:path_src].nil?
|
|
@options[:path_inc] = @options[:path_src] if @options[:path_inc].nil?
|
|
@options[:path_tst] = "#{__dir__}/../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
|