- add ceedling/cmock/unity as testing framework and support

- unified makefile project for the whole repos
- new separate project for tests
This commit is contained in:
hathach 2012-12-27 02:52:40 +07:00
parent f4fa62e032
commit bc735bbe22
157 changed files with 12981 additions and 292 deletions

289
.cproject
View File

@ -269,294 +269,7 @@
</scannerConfigBuildInfo> </scannerConfigBuildInfo>
</storageModule> </storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"> <storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
<buildTargets> <buildTargets/>
<target name="test local" path="tests/tests" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildTarget>test:all</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="test clean" path="tests/tests" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildArguments/>
<buildTarget>clean</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
</buildTargets>
</storageModule>
</cconfiguration>
<cconfiguration id="com.crt.advproject.toolchain.exe.debug.1281391870.909223166">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="com.crt.advproject.toolchain.exe.debug.1281391870.909223166" moduleId="org.eclipse.cdt.core.settings" name="test">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildProperties="" description="" id="com.crt.advproject.toolchain.exe.debug.1281391870.909223166" name="test" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="com.crt.advproject.toolchain.exe.debug.1281391870.909223166." name="/" resourcePath="">
<toolChain id="org.eclipse.cdt.build.core.prefbase.toolchain.437810642" name="No ToolChain" superClass="org.eclipse.cdt.build.core.prefbase.toolchain">
<targetPlatform id="org.eclipse.cdt.build.core.prefbase.toolchain.437810642.512375678" name=""/>
<builder buildPath="${workspace_loc:/tinyusb/test}" id="org.eclipse.cdt.build.core.settings.default.builder.225884486" name="Gnu Make Builder.test" superClass="org.eclipse.cdt.build.core.settings.default.builder"/>
<tool id="org.eclipse.cdt.build.core.settings.holder.libs.48714889" name="holder for library settings" superClass="org.eclipse.cdt.build.core.settings.holder.libs"/>
</toolChain>
</folderInfo>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
<storageModule moduleId="org.eclipse.cdt.core.language.mapping"/>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<profile id="com.crt.advproject.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="arm-none-eabi-c++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="com.crt.advproject.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file} " command="arm-none-eabi-gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="com.crt.advproject.GASManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-x assembler-with-cpp -E -P -v -dD ${plugin_state_location}/${specs_file}" command="arm-none-eabi-gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="makefileGenerator">
<runAction arguments="-E -P -v -dD" command="" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/${specs_file}&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'g++ -E -P -v -dD &quot;${plugin_state_location}/specs.cpp&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/specs.c&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<scannerConfigBuildInfo instanceId="com.crt.advproject.toolchain.exe.debug.1281391870.909223166;com.crt.advproject.toolchain.exe.debug.1281391870.909223166.;cdt.managedbuild.tool.gnu.c.compiler.mingw.base.1123347799;cdt.managedbuild.tool.gnu.c.compiler.input.1476468867">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"/>
<profile id="com.crt.advproject.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="arm-none-eabi-c++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="com.crt.advproject.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file} " command="arm-none-eabi-gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="com.crt.advproject.GASManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-x assembler-with-cpp -E -P -v -dD ${plugin_state_location}/${specs_file}" command="arm-none-eabi-gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="makefileGenerator">
<runAction arguments="-E -P -v -dD" command="" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/${specs_file}&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'g++ -E -P -v -dD &quot;${plugin_state_location}/specs.cpp&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/specs.c&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
<buildTargets>
<target name="test local" path="tests/tests" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildTarget>test:all</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="test clean" path="tests/tests" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildArguments/>
<buildTarget>clean</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
</buildTargets>
</storageModule> </storageModule>
</cconfiguration> </cconfiguration>
</storageModule> </storageModule>

View File

@ -29,10 +29,6 @@
<key>org.eclipse.cdt.make.core.buildCommand</key> <key>org.eclipse.cdt.make.core.buildCommand</key>
<value>make</value> <value>make</value>
</dictionary> </dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.buildLocation</key>
<value>${workspace_loc:/tinyusb/test}</value>
</dictionary>
<dictionary> <dictionary>
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key> <key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
<value>clean</value> <value>clean</value>

199
tests/.cproject Normal file
View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?>
<cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cygwin.base.1801115102">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cygwin.base.1801115102" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.PE" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cygwin.base.1801115102" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.cygwin.base.1801115102.535344002" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.mingw.base.1698092731" name="MinGW GCC" superClass="cdt.managedbuild.toolchain.gnu.mingw.base">
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.PE" id="cdt.managedbuild.target.gnu.platform.mingw.base.1102682489" name="Debug Platform" osList="win32" superClass="cdt.managedbuild.target.gnu.platform.mingw.base"/>
<builder buildPath="${workspace_loc:/tests/Default}" id="cdt.managedbuild.target.gnu.builder.base.2116698513" keepEnvironmentInBuildfile="false" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.base"/>
<tool id="cdt.managedbuild.tool.gnu.assembler.mingw.base.106110968" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.mingw.base">
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.836546927" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.archiver.mingw.base.110080584" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.mingw.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.mingw.base.749914640" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.mingw.base"/>
<tool id="cdt.managedbuild.tool.gnu.c.compiler.mingw.base.2075372140" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.mingw.base">
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.519102576" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.mingw.base.621685388" name="MinGW C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.mingw.base">
<inputType id="cdt.managedbuild.tool.gnu.c.linker.input.145062980" superClass="cdt.managedbuild.tool.gnu.c.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.mingw.base.1931278240" name="MinGW C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.mingw.base"/>
</toolChain>
</folderInfo>
</configuration>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<profile id="com.crt.advproject.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="arm-none-eabi-c++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="com.crt.advproject.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file} " command="arm-none-eabi-gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="com.crt.advproject.GASManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="false"/>
</buildOutputProvider>
<scannerInfoProvider id="com.crt.advproject.specsFile">
<runAction arguments="-x assembler-with-cpp -E -P -v -dD ${plugin_state_location}/${specs_file}" command="arm-none-eabi-gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="makefileGenerator">
<runAction arguments="-E -P -v -dD" command="" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/${specs_file}&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'g++ -E -P -v -dD &quot;${plugin_state_location}/specs.cpp&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/specs.c&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
<storageModule moduleId="org.eclipse.cdt.core.language.mapping"/>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
<buildTargets>
<target name="default" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildArguments/>
<buildTarget>default</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="tests" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildArguments/>
<buildTarget>test:all</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="test delta" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildTarget>test:delta</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="clean" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>rake.bat</buildCommand>
<buildArguments/>
<buildTarget>clean</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>false</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
</buildTargets>
</storageModule>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="tests.null.1788750245" name="tests"/>
</storageModule>
</cproject>

82
tests/.project Normal file
View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>tests</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
<dictionary>
<key>?name?</key>
<value></value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.append_environment</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.autoBuildTarget</key>
<value>all</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.buildArguments</key>
<value></value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.buildCommand</key>
<value>make</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.buildLocation</key>
<value>${workspace_loc:/tests/Default}</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
<value>clean</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.contents</key>
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
<value>false</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.fullBuildTarget</key>
<value>all</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.stopOnError</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
<value>true</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>

78
tests/project.yml Normal file
View File

@ -0,0 +1,78 @@
---
# Notes:
# Sample project C code is not presently written to produce a release artifact.
# As such, release build options are disabled.
# This sample, therefore, only demonstrates running a collection of unit tests.
:project:
:use_exceptions: FALSE
:use_test_preprocessor: TRUE
:use_auxiliary_dependencies: TRUE
:use_deep_dependencies: FALSE
:build_root: build
# :release_build: TRUE
:test_file_prefix: test_
:release_build:
:output: test_tinyusb.exe
:use_assembly: FALSE
:environment:
:extension:
:executable: .exe
:paths:
:test:
- +:test/**
- -:test/support
:source:
- src/**
- ../../tinyusb/**
- ../../../CMSISv2p10_LPC43xx_DriverLib/inc
:support:
- test/support
:defines:
# in order to add common defines:
# 1) remove the trailing [] from the :common: section
# 2) add entries to the :common: section (e.g. :test: has TEST defined)
:commmon: &common_defines []
:test:
- *common_defines
- _TEST_
- MCU=MCU_LPC43XX
- CORE_M4
:test_preprocess:
- *common_defines
- _TEST_
- MCU=MCU_LPC43XX
- CORE_M4
:cmock:
:mock_prefix: mock_
:when_no_prototypes: :warn
:enforce_strict_ordering: TRUE
:plugins:
- :ignore
- :callback
:treat_as:
uint8: HEX8
uint16: HEX16
uint32: UINT32
int8: INT8
bool: UINT8
#:tools:
# Ceedling defaults to using gcc for compiling, linking, etc.
# As [:tools] is blank, gcc will be used (so long as it's in your system path)
# See documentation to configure a given toolchain for use
:plugins:
:load_paths:
- vendor/ceedling/plugins
:enabled:
- stdout_pretty_tests_report
- module_generator
...

4
tests/rakefile.rb Normal file
View File

@ -0,0 +1,4 @@
PROJECT_CEEDLING_ROOT = "vendor/ceedling"
load "#{PROJECT_CEEDLING_ROOT}/lib/rakefile.rb"
task :default => %w[ test:all release ]

View File

@ -0,0 +1,56 @@
/*
* test_main.c
*
* Created on: Dec 18, 2012
* Author: hathach
*/
/*
* Software License Agreement (BSD License)
* Copyright (c) 2012, hathach (tinyusb.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the tiny usb stack.
*/
#include "unity.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_always_succeed()
{
}
void test_always_fail()
{
TEST_FAIL();
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,27 @@
require 'constants'
class BuildInvokerUtils
constructor :configurator, :streaminator
def process_exception(exception, context, test_build=true)
if (exception.message =~ /Don't know how to build task '(.+)'/i)
error_header = "ERROR: Rake could not find file referenced in source"
error_header += " or test" if (test_build)
error_header += ": '#{$1}'. Possible stale dependency."
@streaminator.stderr_puts( error_header )
if (@configurator.project_use_deep_dependencies)
help_message = "Try fixing #include statements or adding missing file. Then run '#{REFRESH_TASK_ROOT}#{context.to_s}' task and try again."
@streaminator.stderr_puts( help_message )
end
raise ''
else
raise exception
end
end
end

View File

@ -0,0 +1,42 @@
class Cacheinator
constructor :cacheinator_helper, :file_path_utils, :file_wrapper, :yaml_wrapper
def cache_test_config(hash)
@yaml_wrapper.dump( @file_path_utils.form_test_build_cache_path( INPUT_CONFIGURATION_CACHE_FILE), hash )
end
def cache_release_config(hash)
@yaml_wrapper.dump( @file_path_utils.form_release_build_cache_path( INPUT_CONFIGURATION_CACHE_FILE ), hash )
end
def diff_cached_test_file( filepath )
cached_filepath = @file_path_utils.form_test_build_cache_path( filepath )
if (@file_wrapper.exist?( cached_filepath ) and (!@file_wrapper.compare( filepath, cached_filepath )))
@file_wrapper.cp(filepath, cached_filepath, {:preserve => false})
return filepath
elsif (!@file_wrapper.exist?( cached_filepath ))
@file_wrapper.cp(filepath, cached_filepath, {:preserve => false})
return filepath
end
return cached_filepath
end
def diff_cached_test_config?(hash)
cached_filepath = @file_path_utils.form_test_build_cache_path(INPUT_CONFIGURATION_CACHE_FILE)
return @cacheinator_helper.diff_cached_config?( cached_filepath, hash )
end
def diff_cached_release_config?(hash)
cached_filepath = @file_path_utils.form_release_build_cache_path(INPUT_CONFIGURATION_CACHE_FILE)
return @cacheinator_helper.diff_cached_config?( cached_filepath, hash )
end
end

View File

@ -0,0 +1,12 @@
class CacheinatorHelper
constructor :file_wrapper, :yaml_wrapper
def diff_cached_config?(cached_filepath, hash)
return true if ( not @file_wrapper.exist?(cached_filepath) )
return true if ( (@file_wrapper.exist?(cached_filepath)) and (!(@yaml_wrapper.load(cached_filepath) == hash)) )
return false
end
end

27
tests/vendor/ceedling/lib/ceedling.rb vendored Normal file
View File

@ -0,0 +1,27 @@
require 'rake'
ERR_MSG = <<EOF
I expected to see a project.yml file in the current directoy. Please create one
by hand or by using the 'ceedling' shell command.
EOF
def ceedling_dir
File.join(
File.dirname(__FILE__),
'..')
end
def builtin_ceedling_plugins_path
File.join(
ceedling_dir,
'plugins')
end
ceeling_lib_rakefile = File.join( ceedling_dir,
'lib',
'rakefile.rb')
if File.exists? "./project.yml"
load ceeling_lib_rakefile
else
$stderr.puts ERR_MSG
end

View File

@ -0,0 +1,16 @@
# @private
module Ceedling
module Version
# @private
GEM = "0.13.0"
# @private
CEEDLING = GEM
# @private
CEXCEPTION = "1.2.17"
# @private
CMOCK = "2.0.215"
# @private
UNITY = "2.1.0"
end
end

View File

@ -0,0 +1,16 @@
# @private
module Ceedling
module Version
# @private
GEM = "0.11.2"
# @private
CEEDLING = "<%= versions["CEEDLING"] %>"
# @private
CEXCEPTION = "<%= versions["CEXCEPTION"] %>"
# @private
CMOCK = "<%= versions["CMOCK"] %>"
# @private
UNITY = "<%= versions["UNITY"] %>"
end
end

View File

@ -0,0 +1,15 @@
require 'cmock'
class CmockBuilder
attr_accessor :cmock
def setup
@cmock = nil
end
def manufacture(cmock_config)
@cmock = CMock.new(cmock_config)
end
end

View File

@ -0,0 +1,329 @@
require 'defaults'
require 'constants'
require 'file_path_utils'
require 'deep_merge'
class Configurator
attr_reader :project_config_hash, :script_plugins, :rake_plugins
attr_accessor :project_logging, :project_debug, :project_verbosity, :sanity_checks
constructor(:configurator_setup, :configurator_builder, :configurator_plugins, :cmock_builder, :yaml_wrapper, :system_wrapper) do
@project_logging = false
@project_debug = false
@project_verbosity = Verbosity::NORMAL
@sanity_checks = TestResultsSanityChecks::NORMAL
end
def setup
# special copy of cmock config to provide to cmock for construction
@cmock_config_hash = {}
# note: project_config_hash is an instance variable so constants and accessors created
# in eval() statements in build() have something of proper scope and persistence to reference
@project_config_hash = {}
@project_config_hash_backup = {}
@script_plugins = []
@rake_plugins = []
end
def replace_flattened_config(config)
@project_config_hash.merge!(config)
@configurator_setup.build_constants_and_accessors(@project_config_hash, binding())
end
def store_config
@project_config_hash_backup = @project_config_hash.clone
end
def restore_config
@project_config_hash = @project_config_hash_backup
@configurator_setup.build_constants_and_accessors(@project_config_hash, binding())
end
def reset_defaults(config)
[:test_compiler,
:test_linker,
:test_fixture,
:test_includes_preprocessor,
:test_file_preprocessor,
:test_dependencies_generator,
:release_compiler,
:release_assembler,
:release_linker,
:release_dependencies_generator].each do |tool|
config[:tools].delete(tool) if (not (config[:tools][tool].nil?))
end
end
def populate_defaults(config)
new_config = DEFAULT_CEEDLING_CONFIG.deep_clone
new_config.deep_merge!(config)
config.replace(new_config)
@configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST )
@configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST_PREPROCESSORS ) if (config[:project][:use_test_preprocessor])
@configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST_DEPENDENCIES ) if (config[:project][:use_deep_dependencies])
@configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE ) if (config[:project][:release_build])
@configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE_ASSEMBLER ) if (config[:project][:release_build] and config[:release_build][:use_assembly])
@configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE_DEPENDENCIES ) if (config[:project][:release_build] and config[:project][:use_deep_dependencies])
end
def populate_cmock_defaults(config)
# cmock has its own internal defaults handling, but we need to set these specific values
# so they're present for the build environment to access;
# note: these need to end up in the hash given to initialize cmock for this to be successful
cmock = config[:cmock] || {}
# yes, we're duplicating the default mock_prefix in cmock, but it's because we need CMOCK_MOCK_PREFIX always available in Ceedling's environment
cmock[:mock_prefix] = 'Mock' if (cmock[:mock_prefix].nil?)
# just because strict ordering is the way to go
cmock[:enforce_strict_ordering] = true if (cmock[:enforce_strict_ordering].nil?)
cmock[:mock_path] = File.join(config[:project][:build_root], TESTS_BASE_PATH, 'mocks') if (cmock[:mock_path].nil?)
cmock[:verbosity] = @project_verbosity if (cmock[:verbosity].nil?)
cmock[:plugins] = [] if (cmock[:plugins].nil?)
cmock[:plugins].map! { |plugin| plugin.to_sym }
cmock[:plugins] << (:cexception) if (!cmock[:plugins].include?(:cexception) and (config[:project][:use_exceptions]))
cmock[:plugins].uniq!
cmock[:unity_helper] = false if (cmock[:unity_helper].nil?)
if (cmock[:unity_helper])
cmock[:includes] << File.basename(cmock[:unity_helper])
cmock[:includes].uniq!
end
@runner_config = cmock.merge(config[:test_runner] || {})
@cmock_builder.manufacture(cmock)
end
def get_runner_config
@runner_config
end
# grab tool names from yaml and insert into tool structures so available for error messages
# set up default values
def tools_setup(config)
config[:tools].each_key do |name|
tool = config[:tools][name]
# populate name if not given
tool[:name] = name.to_s if (tool[:name].nil?)
# populate stderr redirect option
tool[:stderr_redirect] = StdErrRedirect::NONE if (tool[:stderr_redirect].nil?)
# populate background execution option
tool[:background_exec] = BackgroundExec::NONE if (tool[:background_exec].nil?)
# populate optional option to control verification of executable in search paths
tool[:optional] = false if (tool[:optional].nil?)
end
end
def tools_supplement_arguments(config)
tools_name_prefix = 'tools_'
config[:tools].each_key do |name|
tool = @project_config_hash[(tools_name_prefix + name.to_s).to_sym]
# smoosh in extra arguments if specified at top-level of config (useful for plugins & default gcc tools)
# arguments are squirted in at beginning of list
top_level_tool = (tools_name_prefix + name.to_s).to_sym
if (not config[top_level_tool].nil?)
# adding and flattening is not a good idea: might over-flatten if there's array nesting in tool args
# use _with_index to preserve order
config[top_level_tool][:arguments].each_with_index { |arg, index| tool[:arguments].insert( index, arg ) }
end
end
end
def find_and_merge_plugins(config)
# plugins must be loaded before generic path evaluation & magic that happen later;
# perform path magic here as discrete step
config[:plugins][:load_paths].each do |path|
path.replace(@system_wrapper.module_eval(path)) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN)
FilePathUtils::standardize(path)
end
paths_hash = @configurator_plugins.add_load_paths(config)
@rake_plugins = @configurator_plugins.find_rake_plugins(config)
@script_plugins = @configurator_plugins.find_script_plugins(config)
config_plugins = @configurator_plugins.find_config_plugins(config)
plugin_defaults = @configurator_plugins.find_plugin_defaults(config)
config_plugins.each do |plugin|
config.deep_merge( @yaml_wrapper.load(plugin) )
end
plugin_defaults.each do |defaults|
@configurator_builder.populate_defaults( config, @yaml_wrapper.load(defaults) )
end
# special plugin setting for results printing
config[:plugins][:display_raw_test_results] = true if (config[:plugins][:display_raw_test_results].nil?)
paths_hash.each_pair { |name, path| config[:plugins][name] = path }
end
def eval_environment_variables(config)
config[:environment].each do |hash|
key = hash.keys[0]
value = hash[key]
items = []
interstitial = ((key == :path) ? File::PATH_SEPARATOR : '')
items = ((value.class == Array) ? hash[key] : [value])
items.each { |item| item.replace( @system_wrapper.module_eval( item ) ) if (item =~ RUBY_STRING_REPLACEMENT_PATTERN) }
hash[key] = items.join( interstitial )
@system_wrapper.env_set( key.to_s.upcase, hash[key] )
end
end
def eval_paths(config)
# [:plugins]:[load_paths] already handled
paths = [ # individual paths that don't follow convention processed below
config[:project][:build_root],
config[:release_build][:artifacts]]
eval_path_list( paths )
config[:paths].each_pair { |collection, paths| eval_path_list( paths ) }
config[:files].each_pair { |collection, files| eval_path_list( paths ) }
# all other paths at secondary hash key level processed by convention:
# ex. [:toplevel][:foo_path] & [:toplevel][:bar_paths] are evaluated
config.each_pair { |parent, child| eval_path_list( collect_path_list( child ) ) }
end
def standardize_paths(config)
# [:plugins]:[load_paths] already handled
paths = [ # individual paths that don't follow convention processed below
config[:project][:build_root],
config[:release_build][:artifacts]] # cmock path in case it was explicitly set in config
paths.flatten.each { |path| FilePathUtils::standardize( path ) }
config[:paths].each_pair do |collection, paths|
paths.each{|path| FilePathUtils::standardize( path )}
# ensure that list is an array (i.e. handle case of list being a single string)
config[:paths][collection] = [paths].flatten
end
config[:files].each_pair { |collection, files| files.each{ |path| FilePathUtils::standardize( path ) } }
config[:tools].each_pair { |tool, config| FilePathUtils::standardize( config[:executable] ) }
# all other paths at secondary hash key level processed by convention:
# ex. [:toplevel][:foo_path] & [:toplevel][:bar_paths] are standardized
config.each_pair do |parent, child|
collect_path_list( child ).each { |path| FilePathUtils::standardize( path ) }
end
end
def validate(config)
# collect felonies and go straight to jail
raise if (not @configurator_setup.validate_required_sections( config ))
# collect all misdemeanors, everybody on probation
blotter = []
blotter << @configurator_setup.validate_required_section_values( config )
blotter << @configurator_setup.validate_paths( config )
blotter << @configurator_setup.validate_tools( config )
blotter << @configurator_setup.validate_plugins( config )
raise if (blotter.include?( false ))
end
# create constants and accessors (attached to this object) from given hash
def build(config, *keys)
# create flattened & expanded configuration hash
built_config = @configurator_setup.build_project_config( config, @configurator_builder.flattenify( config ) )
@project_config_hash = built_config.clone
store_config()
@configurator_setup.build_constants_and_accessors(built_config, binding())
# top-level keys disappear when we flatten, so create global constants & accessors to any specified keys
keys.each do |key|
hash = { key => config[key] }
@configurator_setup.build_constants_and_accessors(hash, binding())
end
end
# add to constants and accessors as post build step
def build_supplement(config_base, config_more)
# merge in our post-build additions to base configuration hash
config_base.deep_merge!( config_more )
# flatten our addition hash
config_more_flattened = @configurator_builder.flattenify( config_more )
# merge our flattened hash with built hash from previous build
@project_config_hash.deep_merge!( config_more_flattened )
store_config()
# create more constants and accessors
@configurator_setup.build_constants_and_accessors(config_more_flattened, binding())
# recreate constants & update accessors with new merged, base values
config_more.keys.each do |key|
hash = { key => config_base[key] }
@configurator_setup.build_constants_and_accessors(hash, binding())
end
end
def insert_rake_plugins(plugins)
plugins.each do |plugin|
@project_config_hash[:project_rakefile_component_files] << plugin
end
end
### private ###
private
def collect_path_list( container )
paths = []
container.each_key { |key| paths << container[key] if (key.to_s =~ /_path(s)?$/) } if (container.class == Hash)
return paths.flatten
end
def eval_path_list( paths )
paths.flatten.each do |path|
path.replace( @system_wrapper.module_eval( path ) ) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN)
end
end
end

View File

@ -0,0 +1,437 @@
require 'rubygems'
require 'rake' # for ext() method
require 'file_path_utils' # for class methods
require 'defaults'
require 'constants' # for Verbosity constants class & base file paths
class ConfiguratorBuilder
constructor :file_system_utils, :file_wrapper, :system_wrapper
def build_global_constants(config)
config.each_pair do |key, value|
formatted_key = key.to_s.upcase
# undefine global constant if it already exists
Object.send(:remove_const, formatted_key.to_sym) if @system_wrapper.constants_include?(formatted_key)
# create global constant
Object.module_eval("#{formatted_key} = value")
end
end
def build_accessor_methods(config, context)
config.each_pair do |key, value|
# fill configurator object with accessor methods
eval("def #{key.to_s.downcase}() return @project_config_hash[:#{key.to_s}] end", context)
end
end
# create a flattened hash from the original configuration structure
def flattenify(config)
new_hash = {}
config.each_key do | parent |
# gracefully handle empty top-level entries
next if (config[parent].nil?)
case config[parent]
when Array
config[parent].each do |hash|
key = "#{parent.to_s.downcase}_#{hash.keys[0].to_s.downcase}".to_sym
new_hash[key] = hash[hash.keys[0]]
end
when Hash
config[parent].each_pair do | child, value |
key = "#{parent.to_s.downcase}_#{child.to_s.downcase}".to_sym
new_hash[key] = value
end
# handle entries with no children, only values
else
new_hash["#{parent.to_s.downcase}".to_sym] = config[parent]
end
end
return new_hash
end
def populate_defaults(config, defaults)
defaults.keys.sort.each do |section|
defaults[section].keys.sort.each do |entry|
config[section][entry] = defaults[section][entry].deep_clone if (config[section].nil? or config[section][entry].nil?)
end
end
end
def clean(in_hash)
# ensure that include files inserted into test runners have file extensions & proper ones at that
in_hash[:test_runner_includes].map!{|include| include.ext(in_hash[:extension_header])}
end
def set_build_paths(in_hash)
out_hash = {}
project_build_artifacts_root = File.join(in_hash[:project_build_root], 'artifacts')
project_build_tests_root = File.join(in_hash[:project_build_root], TESTS_BASE_PATH)
project_build_release_root = File.join(in_hash[:project_build_root], RELEASE_BASE_PATH)
paths = [
[:project_build_artifacts_root, project_build_artifacts_root, true ],
[:project_build_tests_root, project_build_tests_root, true ],
[:project_build_release_root, project_build_release_root, in_hash[:project_release_build] ],
[:project_test_artifacts_path, File.join(project_build_artifacts_root, TESTS_BASE_PATH), true ],
[:project_test_runners_path, File.join(project_build_tests_root, 'runners'), true ],
[:project_test_results_path, File.join(project_build_tests_root, 'results'), true ],
[:project_test_build_output_path, File.join(project_build_tests_root, 'out'), true ],
[:project_test_build_cache_path, File.join(project_build_tests_root, 'cache'), true ],
[:project_test_dependencies_path, File.join(project_build_tests_root, 'dependencies'), true ],
[:project_release_artifacts_path, File.join(project_build_artifacts_root, RELEASE_BASE_PATH), in_hash[:project_release_build] ],
[:project_release_build_cache_path, File.join(project_build_release_root, 'cache'), in_hash[:project_release_build] ],
[:project_release_build_output_path, File.join(project_build_release_root, 'out'), in_hash[:project_release_build] ],
[:project_release_build_output_asm_path, File.join(project_build_release_root, 'out', 'asm'), in_hash[:project_release_build] ],
[:project_release_build_output_c_path, File.join(project_build_release_root, 'out', 'c'), in_hash[:project_release_build] ],
[:project_release_dependencies_path, File.join(project_build_release_root, 'dependencies'), in_hash[:project_release_build] ],
[:project_log_path, File.join(in_hash[:project_build_root], 'logs'), true ],
[:project_temp_path, File.join(in_hash[:project_build_root], 'temp'), true ],
[:project_test_preprocess_includes_path, File.join(project_build_tests_root, 'preprocess/includes'), in_hash[:project_use_test_preprocessor] ],
[:project_test_preprocess_files_path, File.join(project_build_tests_root, 'preprocess/files'), in_hash[:project_use_test_preprocessor] ],
]
out_hash[:project_build_paths] = []
# fetch already set mock path
out_hash[:project_build_paths] << in_hash[:cmock_mock_path] if (in_hash[:project_use_mocks])
paths.each do |path|
build_path_name = path[0]
build_path = path[1]
build_path_add_condition = path[2]
# insert path into build paths if associated with true condition
out_hash[:project_build_paths] << build_path if build_path_add_condition
# set path symbol name and path for each entry in paths array
out_hash[build_path_name] = build_path
end
return out_hash
end
def set_force_build_filepaths(in_hash)
out_hash = {}
out_hash[:project_test_force_rebuild_filepath] = File.join( in_hash[:project_test_dependencies_path], 'force_build' )
out_hash[:project_release_force_rebuild_filepath] = File.join( in_hash[:project_release_dependencies_path], 'force_build' ) if (in_hash[:project_release_build])
return out_hash
end
def set_rakefile_components(in_hash)
out_hash = {
:project_rakefile_component_files =>
[File.join(CEEDLING_LIB, 'tasks_base.rake'),
File.join(CEEDLING_LIB, 'tasks_filesystem.rake'),
File.join(CEEDLING_LIB, 'tasks_tests.rake'),
File.join(CEEDLING_LIB, 'tasks_vendor.rake'),
File.join(CEEDLING_LIB, 'rules_tests.rake')]}
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'rules_cmock.rake') if (in_hash[:project_use_mocks])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'rules_preprocess.rake') if (in_hash[:project_use_test_preprocessor])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'rules_tests_deep_dependencies.rake') if (in_hash[:project_use_deep_dependencies])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'tasks_tests_deep_dependencies.rake') if (in_hash[:project_use_deep_dependencies])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'rules_release_deep_dependencies.rake') if (in_hash[:project_release_build] and in_hash[:project_use_deep_dependencies])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'rules_release.rake') if (in_hash[:project_release_build])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'tasks_release_deep_dependencies.rake') if (in_hash[:project_release_build] and in_hash[:project_use_deep_dependencies])
out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'tasks_release.rake') if (in_hash[:project_release_build])
return out_hash
end
def set_library_build_info_filepaths(hash)
# Notes:
# - Dependency on a change to our input configuration hash is handled elsewhere as it is
# dynamically formed during ceedling's execution
# - Compiled vendor dependencies like cmock.o, unity.o, cexception.o are handled below;
# here we're interested only in ceedling-based code generation dependencies
ceedling_build_info_filepath = File.join(CEEDLING_RELEASE, 'build.info')
cmock_build_info_filepath = FilePathUtils::form_ceedling_vendor_path('cmock/release', 'build.info')
out_hash = {
:ceedling_build_info_filepath => ceedling_build_info_filepath,
:cmock_build_info_filepath => cmock_build_info_filepath
}
return out_hash
end
def set_release_target(in_hash)
return {} if (not in_hash[:project_release_build])
release_target_file = ((in_hash[:release_build_output].nil?) ? (DEFAULT_RELEASE_TARGET_NAME.ext(in_hash[:extension_executable])) : in_hash[:release_build_output])
release_map_file = ((in_hash[:release_build_output].nil?) ? (DEFAULT_RELEASE_TARGET_NAME.ext(in_hash[:extension_map])) : in_hash[:release_build_output].ext(in_hash[:extension_map]))
return {
# tempted to make a helper method in file_path_utils? stop right there, pal. you'll introduce a cyclical dependency
:project_release_build_target => File.join(in_hash[:project_build_release_root], release_target_file),
:project_release_build_map => File.join(in_hash[:project_build_release_root], release_map_file)
}
end
def collect_project_options(in_hash)
options = []
in_hash[:project_options_paths].each do |path|
options << @file_wrapper.directory_listing( File.join(path, '*.yml') )
end
return {
:collection_project_options => options.flatten
}
end
def expand_all_path_globs(in_hash)
out_hash = {}
path_keys = []
in_hash.each_key do |key|
next if (not key.to_s[0..4] == 'paths')
path_keys << key
end
# sorted to provide assured order of traversal in test calls on mocks
path_keys.sort.each do |key|
out_hash["collection_#{key.to_s}".to_sym] = @file_system_utils.collect_paths( in_hash[key] )
end
return out_hash
end
def collect_source_and_include_paths(in_hash)
return {
:collection_paths_source_and_include =>
( in_hash[:collection_paths_source] +
in_hash[:collection_paths_include] ).select {|x| File.directory?(x)}
}
end
def collect_source_include_vendor_paths(in_hash)
extra_paths = []
extra_paths << FilePathUtils::form_ceedling_vendor_path(CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions])
return {
:collection_paths_source_include_vendor =>
in_hash[:collection_paths_source_and_include] +
extra_paths
}
end
def collect_test_support_source_include_paths(in_hash)
return {
:collection_paths_test_support_source_include =>
(in_hash[:collection_paths_test] +
in_hash[:collection_paths_support] +
in_hash[:collection_paths_source] +
in_hash[:collection_paths_include] ).select {|x| File.directory?(x)}
}
end
def collect_vendor_paths(in_hash)
return {:collection_paths_vendor => get_vendor_paths(in_hash)}
end
def collect_test_support_source_include_vendor_paths(in_hash)
return {
:collection_paths_test_support_source_include_vendor =>
in_hash[:collection_paths_test_support_source_include] +
get_vendor_paths(in_hash)
}
end
def collect_tests(in_hash)
all_tests = @file_wrapper.instantiate_file_list
in_hash[:collection_paths_test].each do |path|
all_tests.include( File.join(path, "#{in_hash[:project_test_file_prefix]}*#{in_hash[:extension_source]}") )
end
@file_system_utils.revise_file_list( all_tests, in_hash[:files_test] )
return {:collection_all_tests => all_tests}
end
def collect_assembly(in_hash)
all_assembly = @file_wrapper.instantiate_file_list
return {:collection_all_assembly => all_assembly} if (not in_hash[:release_build_use_assembly])
in_hash[:collection_paths_source].each do |path|
all_assembly.include( File.join(path, "*#{in_hash[:extension_assembly]}") )
end
@file_system_utils.revise_file_list( all_assembly, in_hash[:files_assembly] )
return {:collection_all_assembly => all_assembly}
end
def collect_source(in_hash)
all_source = @file_wrapper.instantiate_file_list
in_hash[:collection_paths_source].each do |path|
if File.exists?(path) and not File.directory?(path)
all_source.include( path )
else
all_source.include( File.join(path, "*#{in_hash[:extension_source]}") )
end
end
@file_system_utils.revise_file_list( all_source, in_hash[:files_source] )
return {:collection_all_source => all_source}
end
def collect_headers(in_hash)
all_headers = @file_wrapper.instantiate_file_list
paths =
in_hash[:collection_paths_test] +
in_hash[:collection_paths_support] +
in_hash[:collection_paths_source] +
in_hash[:collection_paths_include]
paths.each do |path|
all_headers.include( File.join(path, "*#{in_hash[:extension_header]}") )
end
@file_system_utils.revise_file_list( all_headers, in_hash[:files_include] )
return {:collection_all_headers => all_headers}
end
def collect_all_existing_compilation_input(in_hash)
all_input = @file_wrapper.instantiate_file_list
paths =
in_hash[:collection_paths_test] +
in_hash[:collection_paths_support] +
in_hash[:collection_paths_source] +
in_hash[:collection_paths_include] +
[FilePathUtils::form_ceedling_vendor_path(UNITY_LIB_PATH)]
paths << FilePathUtils::form_ceedling_vendor_path(CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions])
paths << FilePathUtils::form_ceedling_vendor_path(CMOCK_LIB_PATH) if (in_hash[:project_use_mocks])
paths.each do |path|
all_input.include( File.join(path, "*#{in_hash[:extension_header]}") )
if File.exists?(path) and not File.directory?(path)
all_input.include( path )
else
all_input.include( File.join(path, "*#{in_hash[:extension_source]}") )
end
end
@file_system_utils.revise_file_list( all_input, in_hash[:files_test] )
@file_system_utils.revise_file_list( all_input, in_hash[:files_support] )
@file_system_utils.revise_file_list( all_input, in_hash[:files_source] )
@file_system_utils.revise_file_list( all_input, in_hash[:files_include] )
# finding assembly files handled explicitly through other means
return {:collection_all_existing_compilation_input => all_input}
end
def collect_test_and_vendor_defines(in_hash)
test_defines = in_hash[:defines_test].clone
test_defines.concat(in_hash[:unity_defines])
test_defines.concat(in_hash[:cmock_defines]) if (in_hash[:project_use_mocks])
test_defines.concat(in_hash[:cexception_defines]) if (in_hash[:project_use_exceptions])
return {:collection_defines_test_and_vendor => test_defines}
end
def collect_release_and_vendor_defines(in_hash)
release_defines = in_hash[:defines_release].clone
release_defines.concat(in_hash[:cexception_defines]) if (in_hash[:project_use_exceptions])
return {:collection_defines_release_and_vendor => release_defines}
end
def collect_release_artifact_extra_link_objects(in_hash)
objects = []
# no build paths here so plugins can remap if necessary (i.e. path mapping happens at runtime)
objects << CEXCEPTION_C_FILE.ext( in_hash[:extension_object] ) if (in_hash[:project_use_exceptions])
return {:collection_release_artifact_extra_link_objects => objects}
end
def collect_test_fixture_extra_link_objects(in_hash)
# Note: Symbols passed to compiler at command line can change Unity and CException behavior / configuration;
# we also handle those dependencies elsewhere in compilation dependencies
objects = [UNITY_C_FILE]
# we don't include paths here because use of plugins or mixing different compilers may require different build paths
objects << CEXCEPTION_C_FILE if (in_hash[:project_use_exceptions])
objects << CMOCK_C_FILE if (in_hash[:project_use_mocks])
# if we're using mocks & a unity helper is defined & that unity helper includes a source file component (not only a header of macros),
# then link in the unity_helper object file too
if ( in_hash[:project_use_mocks] and
in_hash[:cmock_unity_helper] and
@file_wrapper.exist?(in_hash[:cmock_unity_helper].ext(in_hash[:extension_source])) )
objects << File.basename(in_hash[:cmock_unity_helper])
end
# no build paths here so plugins can remap if necessary (i.e. path mapping happens at runtime)
objects.map! { |object| object.ext(in_hash[:extension_object]) }
return { :collection_test_fixture_extra_link_objects => objects }
end
private
def get_vendor_paths(in_hash)
vendor_paths = []
vendor_paths << FilePathUtils::form_ceedling_vendor_path(UNITY_LIB_PATH)
vendor_paths << FilePathUtils::form_ceedling_vendor_path(CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions])
vendor_paths << FilePathUtils::form_ceedling_vendor_path(CMOCK_LIB_PATH) if (in_hash[:project_use_mocks])
vendor_paths << in_hash[:cmock_mock_path] if (in_hash[:project_use_mocks])
return vendor_paths
end
end

View File

@ -0,0 +1,124 @@
require 'constants'
class ConfiguratorPlugins
constructor :stream_wrapper, :file_wrapper, :system_wrapper
attr_reader :rake_plugins, :script_plugins
def setup
@rake_plugins = []
@script_plugins = []
end
def add_load_paths(config)
plugin_paths = {}
config[:plugins][:load_paths].each do |root|
@system_wrapper.add_load_path( root ) if ( not @file_wrapper.directory_listing( File.join( root, '*.rb' ) ).empty? )
config[:plugins][:enabled].each do |plugin|
path = File.join(root, plugin, "lib")
old_path = File.join( root, plugin )
if ( not @file_wrapper.directory_listing( File.join( path, '*.rb' ) ).empty? )
plugin_paths[(plugin + '_path').to_sym] = path
@system_wrapper.add_load_path( path )
elsif ( not @file_wrapper.directory_listing( File.join( old_path, '*.rb' ) ).empty? )
plugin_paths[(plugin + '_path').to_sym] = old_path
@system_wrapper.add_load_path( old_path )
end
end
end
return plugin_paths
end
# gather up and return .rake filepaths that exist on-disk
def find_rake_plugins(config)
plugins_with_path = []
config[:plugins][:load_paths].each do |root|
config[:plugins][:enabled].each do |plugin|
rake_plugin_path = File.join(root, plugin, "#{plugin}.rake")
if (@file_wrapper.exist?(rake_plugin_path))
plugins_with_path << rake_plugin_path
@rake_plugins << plugin
end
end
end
return plugins_with_path
end
# gather up and return just names of .rb classes that exist on-disk
def find_script_plugins(config)
config[:plugins][:load_paths].each do |root|
config[:plugins][:enabled].each do |plugin|
script_plugin_path = File.join(root, plugin, "lib", "#{plugin}.rb")
# Add the old path here to support legacy style. Eventaully remove.
old_script_plugin_path = File.join(root, plugin, "#{plugin}.rb")
if @file_wrapper.exist?(script_plugin_path) or @file_wrapper.exist?(old_script_plugin_path)
@script_plugins << plugin
end
# Print depreciation warning.
if @file_wrapper.exist?(old_script_plugin_path)
$stderr.puts "WARNING: Depreciated plugin style used in #{plugin}. Use new directory structure!"
end
end
end
return @script_plugins
end
# gather up and return configuration .yml filepaths that exist on-disk
def find_config_plugins(config)
plugins_with_path = []
config[:plugins][:load_paths].each do |root|
config[:plugins][:enabled].each do |plugin|
config_plugin_path = File.join(root, plugin, "config", "#{plugin}.yml")
# Add the old path here to support legacy style. Eventaully remove.
old_config_plugin_path = File.join(root, plugin, "#{plugin}.yml")
if @file_wrapper.exist?(config_plugin_path)
plugins_with_path << config_plugin_path
elsif @file_wrapper.exist?(old_config_plugin_path)
# there's a warning printed for this in find_script_plugins
plugins_with_path << old_config_plugin_path
end
end
end
return plugins_with_path
end
# gather up and return default .yml filepaths that exist on-disk
def find_plugin_defaults(config)
defaults_with_path = []
config[:plugins][:load_paths].each do |root|
config[:plugins][:enabled].each do |plugin|
default_path = File.join(root, plugin, 'config', 'defaults.yml')
old_default_path = File.join(root, plugin, 'defaults.yml')
if @file_wrapper.exist?(default_path)
defaults_with_path << default_path
elsif @file_wrapper.exist?(old_default_path)
defaults_with_path << old_default_path
end
end
end
return defaults_with_path
end
end

View File

@ -0,0 +1,124 @@
# add sort-ability to symbol so we can order keys array in hash for test-ability
class Symbol
include Comparable
def <=>(other)
self.to_s <=> other.to_s
end
end
class ConfiguratorSetup
constructor :configurator_builder, :configurator_validator, :configurator_plugins, :stream_wrapper
def build_project_config(config, flattened_config)
### flesh out config
@configurator_builder.clean(flattened_config)
### add to hash values we build up from configuration & file system contents
flattened_config.merge!(@configurator_builder.set_build_paths(flattened_config))
flattened_config.merge!(@configurator_builder.set_force_build_filepaths(flattened_config))
flattened_config.merge!(@configurator_builder.set_rakefile_components(flattened_config))
flattened_config.merge!(@configurator_builder.set_library_build_info_filepaths(flattened_config))
flattened_config.merge!(@configurator_builder.set_release_target(flattened_config))
flattened_config.merge!(@configurator_builder.collect_project_options(flattened_config))
### iterate through all entries in paths section and expand any & all globs to actual paths
flattened_config.merge!(@configurator_builder.expand_all_path_globs(flattened_config))
flattened_config.merge!(@configurator_builder.collect_vendor_paths(flattened_config))
flattened_config.merge!(@configurator_builder.collect_source_and_include_paths(flattened_config))
flattened_config.merge!(@configurator_builder.collect_source_include_vendor_paths(flattened_config))
flattened_config.merge!(@configurator_builder.collect_test_support_source_include_paths(flattened_config))
flattened_config.merge!(@configurator_builder.collect_test_support_source_include_vendor_paths(flattened_config))
flattened_config.merge!(@configurator_builder.collect_tests(flattened_config))
flattened_config.merge!(@configurator_builder.collect_assembly(flattened_config))
flattened_config.merge!(@configurator_builder.collect_source(flattened_config))
flattened_config.merge!(@configurator_builder.collect_headers(flattened_config))
flattened_config.merge!(@configurator_builder.collect_all_existing_compilation_input(flattened_config))
flattened_config.merge!(@configurator_builder.collect_test_and_vendor_defines(flattened_config))
flattened_config.merge!(@configurator_builder.collect_release_and_vendor_defines(flattened_config))
flattened_config.merge!(@configurator_builder.collect_release_artifact_extra_link_objects(flattened_config))
flattened_config.merge!(@configurator_builder.collect_test_fixture_extra_link_objects(flattened_config))
return flattened_config
end
def build_constants_and_accessors(config, context)
@configurator_builder.build_global_constants(config)
@configurator_builder.build_accessor_methods(config, context)
end
def validate_required_sections(config)
validation = []
validation << @configurator_validator.exists?(config, :project)
validation << @configurator_validator.exists?(config, :paths)
return false if (validation.include?(false))
return true
end
def validate_required_section_values(config)
validation = []
validation << @configurator_validator.exists?(config, :project, :build_root)
validation << @configurator_validator.exists?(config, :paths, :test)
validation << @configurator_validator.exists?(config, :paths, :source)
return false if (validation.include?(false))
return true
end
def validate_paths(config)
validation = []
validation << @configurator_validator.validate_filepath(config, :project, :build_root)
validation << @configurator_validator.validate_filepath(config, :cmock, :unity_helper) if config[:cmock][:unity_helper]
config[:project][:options_paths].each do |path|
validation << @configurator_validator.validate_filepath_simple( path, :project, :options_paths )
end
config[:plugins][:load_paths].each do |path|
validation << @configurator_validator.validate_filepath_simple( path, :plugins, :load_paths )
end
config[:paths].keys.sort.each do |key|
validation << @configurator_validator.validate_path_list(config, :paths, key)
end
return false if (validation.include?(false))
return true
end
def validate_tools(config)
validation = []
config[:tools].keys.sort.each do |key|
validation << @configurator_validator.exists?(config, :tools, key, :executable)
validation << @configurator_validator.validate_executable_filepath(config, :tools, key, :executable) if (not config[:tools][key][:optional])
validation << @configurator_validator.validate_tool_stderr_redirect(config, :tools, key)
end
return false if (validation.include?(false))
return true
end
def validate_plugins(config)
missing_plugins =
Set.new( config[:plugins][:enabled] ) -
Set.new( @configurator_plugins.rake_plugins ) -
Set.new( @configurator_plugins.script_plugins )
missing_plugins.each do |plugin|
@stream_wrapper.stderr_puts("ERROR: Ceedling plugin '#{plugin}' contains no rake or ruby class entry point. (Misspelled or missing files?)")
end
return ( (missing_plugins.size > 0) ? false : true )
end
end

View File

@ -0,0 +1,184 @@
require 'rubygems'
require 'rake' # for ext()
require 'constants'
require 'tool_executor' # for argument replacement pattern
require 'file_path_utils' # for glob handling class methods
class ConfiguratorValidator
constructor :file_wrapper, :stream_wrapper, :system_wrapper
# walk into config hash verify existence of data at key depth
def exists?(config, *keys)
hash = retrieve_value(config, keys)
exist = !hash[:value].nil?
if (not exist)
# no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator
@stream_wrapper.stderr_puts("ERROR: Required config file entry #{format_key_sequence(keys, hash[:depth])} does not exist.")
end
return exist
end
# walk into config hash. verify directory path(s) at given key depth
def validate_path_list(config, *keys)
hash = retrieve_value(config, keys)
list = hash[:value]
# return early if we couldn't walk into hash and find a value
return false if (list.nil?)
path_list = []
exist = true
case list
when String then path_list << list
when Array then path_list = list
end
path_list.each do |path|
base_path = FilePathUtils::extract_path(path) # lop off add/subtract notation & glob specifiers
if (not @file_wrapper.exist?(base_path))
# no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator
@stream_wrapper.stderr_puts("ERROR: Config path #{format_key_sequence(keys, hash[:depth])}['#{base_path}'] does not exist on disk.")
exist = false
end
end
return exist
end
# simple path verification
def validate_filepath_simple(path, *keys)
validate_path = path
if (not @file_wrapper.exist?(validate_path))
# no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator
@stream_wrapper.stderr_puts("ERROR: Config path '#{validate_path}' associated with #{format_key_sequence(keys, keys.size)} does not exist on disk.")
return false
end
return true
end
# walk into config hash. verify specified file exists.
def validate_filepath(config, *keys)
hash = retrieve_value(config, keys)
filepath = hash[:value]
# return early if we couldn't walk into hash and find a value
return false if (filepath.nil?)
# skip everything if we've got an argument replacement pattern
return true if (filepath =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN)
if (not @file_wrapper.exist?(filepath))
# no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator
@stream_wrapper.stderr_puts("ERROR: Config filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist on disk.")
return false
end
return true
end
# walk into config hash. verify specified file exists.
def validate_executable_filepath(config, *keys)
exe_extension = config[:extension][:executable]
hash = retrieve_value(config, keys)
filepath = hash[:value]
# return early if we couldn't walk into hash and find a value
return false if (filepath.nil?)
# skip everything if we've got an argument replacement pattern
return true if (filepath =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN)
# if there's no path included, verify file exists somewhere in system search paths
if (not filepath.include?('/'))
exists = false
@system_wrapper.search_paths.each do |path|
if (@file_wrapper.exist?( File.join(path, filepath)) )
exists = true
break
end
if (@file_wrapper.exist?( (File.join(path, filepath)).ext( exe_extension ) ))
exists = true
break
elsif (@system_wrapper.windows? and @file_wrapper.exist?( (File.join(path, filepath)).ext( EXTENSION_WIN_EXE ) ))
exists = true
break
end
end
if (not exists)
# no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator
@stream_wrapper.stderr_puts("ERROR: Config filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist in system search paths.")
return false
end
# if there is a path included, check that explicit filepath exists
else
if (not @file_wrapper.exist?(filepath))
# no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator
@stream_wrapper.stderr_puts("ERROR: Config filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist on disk.")
return false
end
end
return true
end
def validate_tool_stderr_redirect(config, tools, tool)
redirect = config[tools][tool][:stderr_redirect]
if (redirect.class == Symbol)
# map constants and force to array of strings for runtime universality across ruby versions
if (not StdErrRedirect.constants.map{|constant| constant.to_s}.include?(redirect.to_s.upcase))
error = "ERROR: [:#{tools}][:#{tool}][:stderr_redirect][:#{redirect}] is not a recognized option " +
"{#{StdErrRedirect.constants.map{|constant| ':' + constant.to_s.downcase}.join(', ')}}."
@stream_wrapper.stderr_puts(error)
return false
end
end
return true
end
private #########################################
def retrieve_value(config, keys)
value = nil
hash = config
depth = 0
# walk into hash & extract value at requested key sequence
keys.each do |symbol|
depth += 1
if (not hash[symbol].nil?)
hash = hash[symbol]
value = hash
else
value = nil
break
end
end
return {:value => value, :depth => depth}
end
def format_key_sequence(keys, depth)
walked_keys = keys.slice(0, depth)
formatted_keys = walked_keys.map{|key| "[:#{key.to_s}]"}
return formatted_keys.join
end
end

91
tests/vendor/ceedling/lib/constants.rb vendored Normal file
View File

@ -0,0 +1,91 @@
class Verbosity
SILENT = 0 # as silent as possible (though there are some messages that must be spit out)
ERRORS = 1 # only errors
COMPLAIN = 2 # spit out errors and warnings/notices
NORMAL = 3 # errors, warnings/notices, standard status messages
OBNOXIOUS = 4 # all messages including extra verbose output (used for lite debugging / verification)
DEBUG = 5 # special extra verbose output for hardcore debugging
end
class TestResultsSanityChecks
NONE = 0 # no sanity checking of test results
NORMAL = 1 # perform non-problematic checks
THOROUGH = 2 # perform checks that require inside knowledge of system workings
end
class StdErrRedirect
NONE = :none
AUTO = :auto
WIN = :win
UNIX = :unix
TCSH = :tcsh
end
class BackgroundExec
NONE = :none
AUTO = :auto
WIN = :win
UNIX = :unix
end
EXTENSION_WIN_EXE = '.exe'
EXTENSION_NONWIN_EXE = '.out'
CEXCEPTION_ROOT_PATH = 'c_exception'
CEXCEPTION_LIB_PATH = "#{CEXCEPTION_ROOT_PATH}/lib"
CEXCEPTION_C_FILE = 'CException.c'
CEXCEPTION_H_FILE = 'CException.h'
UNITY_ROOT_PATH = 'unity'
UNITY_LIB_PATH = "#{UNITY_ROOT_PATH}/src"
UNITY_C_FILE = 'unity.c'
UNITY_H_FILE = 'unity.h'
UNITY_INTERNALS_H_FILE = 'unity_internals.h'
CMOCK_ROOT_PATH = 'cmock'
CMOCK_LIB_PATH = "#{CMOCK_ROOT_PATH}/src"
CMOCK_C_FILE = 'cmock.c'
CMOCK_H_FILE = 'cmock.h'
DEFAULT_CEEDLING_MAIN_PROJECT_FILE = 'project.yml' # main project file
DEFAULT_CEEDLING_USER_PROJECT_FILE = 'user.yml' # supplemental user config file
INPUT_CONFIGURATION_CACHE_FILE = 'input.yml' # input configuration file dump
TEST_ROOT_NAME = 'test'
TEST_TASK_ROOT = TEST_ROOT_NAME + ':'
TEST_SYM = TEST_ROOT_NAME.to_sym
RELEASE_ROOT_NAME = 'release'
RELEASE_TASK_ROOT = RELEASE_ROOT_NAME + ':'
RELEASE_SYM = RELEASE_ROOT_NAME.to_sym
REFRESH_ROOT_NAME = 'refresh'
REFRESH_TASK_ROOT = REFRESH_ROOT_NAME + ':'
REFRESH_SYM = REFRESH_ROOT_NAME.to_sym
UTILS_ROOT_NAME = 'utils'
UTILS_TASK_ROOT = UTILS_ROOT_NAME + ':'
UTILS_SYM = UTILS_ROOT_NAME.to_sym
OPERATION_COMPILE_SYM = :compile
OPERATION_LINK_SYM = :link
RUBY_STRING_REPLACEMENT_PATTERN = /#\{.+\}/
RUBY_EVAL_REPLACEMENT_PATTERN = /^\{(.+)\}$/
TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN = /(\$\{(\d+)\})/
TEST_STDOUT_STATISTICS_PATTERN = /-+\s+(\d+)\s+Tests\s+(\d+)\s+Failures\s+(\d+)\s+Ignored\s+(OK|FAIL)\s*/i
NULL_FILE_PATH = '/dev/null'
TESTS_BASE_PATH = TEST_ROOT_NAME
RELEASE_BASE_PATH = RELEASE_ROOT_NAME

380
tests/vendor/ceedling/lib/defaults.rb vendored Normal file
View File

@ -0,0 +1,380 @@
require 'constants'
require 'system_wrapper'
require 'file_path_utils'
DEFAULT_TEST_COMPILER_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_test_compiler'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
{"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze,
{"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze,
{"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze,
"-DGNU_COMPILER".freeze,
"-c \"${1}\"".freeze,
"-o \"${2}\"".freeze,
# gcc's list file output options are complex; no use of ${3} parameter in default config
].freeze
}
DEFAULT_TEST_LINKER_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_test_linker'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
"\"${1}\"".freeze,
"-o \"${2}\"".freeze,
].freeze
}
DEFAULT_TEST_FIXTURE_TOOL = {
:executable => '${1}'.freeze,
:name => 'default_test_fixture'.freeze,
:stderr_redirect => StdErrRedirect::AUTO.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [].freeze
}
DEFAULT_TEST_INCLUDES_PREPROCESSOR_TOOL = {
:executable => FilePathUtils.os_executable_ext('cpp').freeze,
:name => 'default_test_includes_preprocessor'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
'-MM'.freeze,
'-MG'.freeze,
# avoid some possibility of deep system lib header file complications by omitting vendor paths
# if cpp is run on *nix system, escape spaces in paths; if cpp on windows just use the paths collection as is
{"-I\"$\"" => "{SystemWrapper.windows? ? COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE : COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE.map{|path| path.gsub(\/ \/, \'\\\\ \') }}"}.freeze,
{"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze,
{"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze,
"-DGNU_PREPROCESSOR".freeze,
'-w'.freeze,
'-nostdinc'.freeze,
"\"${1}\"".freeze
].freeze
}
DEFAULT_TEST_FILE_PREPROCESSOR_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_test_file_preprocessor'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
'-E'.freeze,
{"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze,
{"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze,
{"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze,
{"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze,
"-DGNU_PREPROCESSOR".freeze,
"\"${1}\"".freeze,
"-o \"${2}\"".freeze
].freeze
}
DEFAULT_TEST_DEPENDENCIES_GENERATOR_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_test_dependencies_generator'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
{"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze,
{"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze,
{"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze,
{"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze,
"-DGNU_PREPROCESSOR".freeze,
"-MT \"${3}\"".freeze,
'-MM'.freeze,
'-MD'.freeze,
'-MG'.freeze,
"-MF \"${2}\"".freeze,
"-c \"${1}\"".freeze,
].freeze
}
DEFAULT_RELEASE_DEPENDENCIES_GENERATOR_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_release_dependencies_generator'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
{"-I\"$\"" => 'COLLECTION_PATHS_SOURCE_INCLUDE_VENDOR'}.freeze,
{"-I\"$\"" => 'COLLECTION_PATHS_RELEASE_TOOLCHAIN_INCLUDE'}.freeze,
{"-D$" => 'COLLECTION_DEFINES_RELEASE_AND_VENDOR'}.freeze,
{"-D$" => 'DEFINES_RELEASE_PREPROCESS'}.freeze,
"-DGNU_PREPROCESSOR".freeze,
"-MT \"${3}\"".freeze,
'-MM'.freeze,
'-MD'.freeze,
'-MG'.freeze,
"-MF \"${2}\"".freeze,
"-c \"${1}\"".freeze,
].freeze
}
DEFAULT_RELEASE_COMPILER_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_release_compiler'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
{"-I\"$\"" => 'COLLECTION_PATHS_SOURCE_INCLUDE_VENDOR'}.freeze,
{"-I\"$\"" => 'COLLECTION_PATHS_RELEASE_TOOLCHAIN_INCLUDE'}.freeze,
{"-D$" => 'COLLECTION_DEFINES_RELEASE_AND_VENDOR'}.freeze,
"-DGNU_COMPILER".freeze,
"-c \"${1}\"".freeze,
"-o \"${2}\"".freeze,
# gcc's list file output options are complex; no use of ${3} parameter in default config
].freeze
}
DEFAULT_RELEASE_ASSEMBLER_TOOL = {
:executable => FilePathUtils.os_executable_ext('as').freeze,
:name => 'default_release_assembler'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
{"-I\"$\"" => 'COLLECTION_PATHS_SOURCE_AND_INCLUDE'}.freeze,
"\"${1}\"".freeze,
"-o \"${2}\"".freeze,
].freeze
}
DEFAULT_RELEASE_LINKER_TOOL = {
:executable => FilePathUtils.os_executable_ext('gcc').freeze,
:name => 'default_release_linker'.freeze,
:stderr_redirect => StdErrRedirect::NONE.freeze,
:background_exec => BackgroundExec::NONE.freeze,
:optional => false.freeze,
:arguments => [
"\"${1}\"".freeze,
"-o \"${2}\"".freeze,
].freeze
}
DEFAULT_TOOLS_TEST = {
:tools => {
:test_compiler => DEFAULT_TEST_COMPILER_TOOL,
:test_linker => DEFAULT_TEST_LINKER_TOOL,
:test_fixture => DEFAULT_TEST_FIXTURE_TOOL,
}
}
DEFAULT_TOOLS_TEST_PREPROCESSORS = {
:tools => {
:test_includes_preprocessor => DEFAULT_TEST_INCLUDES_PREPROCESSOR_TOOL,
:test_file_preprocessor => DEFAULT_TEST_FILE_PREPROCESSOR_TOOL,
}
}
DEFAULT_TOOLS_TEST_DEPENDENCIES = {
:tools => {
:test_dependencies_generator => DEFAULT_TEST_DEPENDENCIES_GENERATOR_TOOL,
}
}
DEFAULT_TOOLS_RELEASE = {
:tools => {
:release_compiler => DEFAULT_RELEASE_COMPILER_TOOL,
:release_linker => DEFAULT_RELEASE_LINKER_TOOL,
}
}
DEFAULT_TOOLS_RELEASE_ASSEMBLER = {
:tools => {
:release_assembler => DEFAULT_RELEASE_ASSEMBLER_TOOL,
}
}
DEFAULT_TOOLS_RELEASE_DEPENDENCIES = {
:tools => {
:release_dependencies_generator => DEFAULT_RELEASE_DEPENDENCIES_GENERATOR_TOOL,
}
}
DEFAULT_RELEASE_TARGET_NAME = 'project'
DEFAULT_CEEDLING_CONFIG = {
:project => {
# :build_root must be set by user
:use_exceptions => true,
:use_mocks => true,
:compile_threads => 1,
:test_threads => 1,
:use_test_preprocessor => false,
:use_deep_dependencies => false,
:test_file_prefix => 'test_',
:options_paths => [],
:release_build => false,
},
:release_build => {
# :output is set while building configuration -- allows smart default system-dependent file extension handling
:use_assembly => false,
:artifacts => [],
},
:paths => {
:test => [], # must be populated by user
:source => [], # must be populated by user
:support => [],
:include => [],
:test_toolchain_include => [],
:release_toolchain_include => [],
},
:files => {
:test => [],
:source => [],
:assembly => [],
:support => [],
:include => [],
},
# unlike other top-level entries, environment's value is an array to preserve order
:environment => [
# when evaluated, this provides wider text field for rake task comments
{:rake_columns => '120'},
],
:defines => {
:test => [],
:test_preprocess => [],
:release => [],
:release_preprocess => [],
},
:flags => {},
:extension => {
:header => '.h',
:source => '.c',
:assembly => '.s',
:object => '.o',
:executable => ( SystemWrapper.windows? ? EXTENSION_WIN_EXE : EXTENSION_NONWIN_EXE ),
:map => '.map',
:list => '.lst',
:testpass => '.pass',
:testfail => '.fail',
:dependencies => '.d',
},
:unity => {
:defines => []
},
:cmock => {
:defines => []
},
:cexception => {
:defines => []
},
:test_runner => {
:includes => [],
:file_suffix => '_runner',
},
# all tools populated while building up config structure
:tools => {},
# empty argument lists for default tools
# (these can be overridden in project file to add arguments to tools without totally redefining tools)
:test_compiler => { :arguments => [] },
:test_linker => { :arguments => [] },
:test_fixture => {
:arguments => [],
:link_objects => [], # compiled object files to always be linked in (e.g. cmock.o if using mocks)
},
:test_includes_preprocessor => { :arguments => [] },
:test_file_preprocessor => { :arguments => [] },
:test_dependencies_generator => { :arguments => [] },
:release_compiler => { :arguments => [] },
:release_linker => { :arguments => [] },
:release_assembler => { :arguments => [] },
:release_dependencies_generator => { :arguments => [] },
:plugins => {
:load_paths => [],
:enabled => [],
}
}.freeze
DEFAULT_TESTS_RESULTS_REPORT_TEMPLATE = %q{
% ignored = hash[:results][:counts][:ignored]
% failed = hash[:results][:counts][:failed]
% stdout_count = hash[:results][:counts][:stdout]
% header_prepend = ((hash[:header].length > 0) ? "#{hash[:header]}: " : '')
% banner_width = 25 + header_prepend.length # widest message
% if (ignored > 0)
<%=@ceedling[:plugin_reportinator].generate_banner(header_prepend + 'IGNORED UNIT TEST SUMMARY')%>
% hash[:results][:ignores].each do |ignore|
% ignore[:collection].each do |item|
<%=ignore[:source][:path]%><%=File::SEPARATOR%><%=ignore[:source][:file]%>:<%=item[:line]%>:<%=item[:test]%>
% if (item[:message].length > 0)
: "<%=item[:message]%>"
% else
<%="\n"%>
% end
% end
% end
% end
% if (failed > 0)
<%=@ceedling[:plugin_reportinator].generate_banner(header_prepend + 'FAILED UNIT TEST SUMMARY')%>
% hash[:results][:failures].each do |failure|
% failure[:collection].each do |item|
<%=failure[:source][:path]%><%=File::SEPARATOR%><%=failure[:source][:file]%>:<%=item[:line]%>:<%=item[:test]%>
% if (item[:message].length > 0)
: "<%=item[:message]%>"
% else
<%="\n"%>
% end
% end
% end
% end
% if (stdout_count > 0)
<%=@ceedling[:plugin_reportinator].generate_banner(header_prepend + 'UNIT TEST OTHER OUTPUT')%>
% hash[:results][:stdout].each do |string|
% string[:collection].each do |item|
<%=string[:source][:path]%><%=File::SEPARATOR%><%=string[:source][:file]%>: "<%=item%>"
% end
% end
% end
% total_string = hash[:results][:counts][:total].to_s
% format_string = "%#{total_string.length}i"
<%=@ceedling[:plugin_reportinator].generate_banner(header_prepend + 'OVERALL UNIT TEST SUMMARY')%>
% if (hash[:results][:counts][:total] > 0)
TESTED: <%=hash[:results][:counts][:total].to_s%>
PASSED: <%=sprintf(format_string, hash[:results][:counts][:passed])%>
FAILED: <%=sprintf(format_string, failed)%>
IGNORED: <%=sprintf(format_string, ignored)%>
% else
No tests executed.
% end
}

View File

@ -0,0 +1,92 @@
class Dependinator
constructor :configurator, :project_config_manager, :test_includes_extractor, :file_path_utils, :rake_wrapper, :file_wrapper
def touch_force_rebuild_files
@file_wrapper.touch( @configurator.project_test_force_rebuild_filepath )
@file_wrapper.touch( @configurator.project_release_force_rebuild_filepath ) if (@configurator.project_release_build)
end
def load_release_object_deep_dependencies(dependencies_list)
dependencies_list.each { |dependencies_file| @rake_wrapper.load_dependencies( dependencies_file ) }
end
def enhance_release_file_dependencies(files)
files.each do |filepath|
@rake_wrapper[filepath].enhance( [@configurator.project_release_force_rebuild_filepath] ) if (@project_config_manager.release_config_changed)
@rake_wrapper[filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
end
def load_test_object_deep_dependencies(files_list)
dependencies_list = @file_path_utils.form_test_dependencies_filelist(files_list)
dependencies_list.each { |dependencies_file| @rake_wrapper.load_dependencies(dependencies_file) }
end
def enhance_runner_dependencies(runner_filepath)
@rake_wrapper[runner_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[runner_filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
def enhance_shallow_include_lists_dependencies(include_lists)
include_lists.each do |include_list_filepath|
@rake_wrapper[include_list_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[include_list_filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
end
def enhance_preprocesed_file_dependencies(files)
files.each do |filepath|
@rake_wrapper[filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
end
def enhance_mock_dependencies(mocks_list)
# if input configuration or ceedling changes, make sure these guys get rebuilt
mocks_list.each do |mock_filepath|
@rake_wrapper[mock_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[mock_filepath].enhance( [@configurator.cmock_unity_helper] ) if (@configurator.cmock_unity_helper)
@rake_wrapper[mock_filepath].enhance( [@configurator.ceedling_build_info_filepath] )
@rake_wrapper[mock_filepath].enhance( [@configurator.cmock_build_info_filepath] )
end
end
def enhance_dependencies_dependencies(dependencies)
dependencies.each do |dependencies_filepath|
@rake_wrapper[dependencies_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[dependencies_filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
end
def enhance_test_build_object_dependencies(objects)
objects.each do |object_filepath|
@rake_wrapper[object_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[object_filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
end
def enhance_results_dependencies(result_filepath)
@rake_wrapper[result_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed)
@rake_wrapper[result_filepath].enhance( [@configurator.ceedling_build_info_filepath] )
end
def setup_test_executable_dependencies(test, objects)
@rake_wrapper.create_file_task( @file_path_utils.form_test_executable_filepath(test), objects)
end
end

View File

@ -0,0 +1,9 @@
require 'erb'
class ErbWrapper
def generate_file(template, data, output_file)
File.open(output_file, "w") do |f|
f << ERB.new(template, 0, "<>").result(binding)
end
end
end

132
tests/vendor/ceedling/lib/file_finder.rb vendored Normal file
View File

@ -0,0 +1,132 @@
require 'rubygems'
require 'rake' # for adding ext() method to string
class FileFinder
constructor :configurator, :file_finder_helper, :cacheinator, :file_path_utils, :file_wrapper, :yaml_wrapper
def prepare_search_sources
@all_test_source_and_header_file_collection =
@configurator.collection_all_tests +
@configurator.collection_all_source +
@configurator.collection_all_headers
end
def find_header_file(mock_file)
header = File.basename(mock_file).sub(/#{@configurator.cmock_mock_prefix}/, '').ext(@configurator.extension_header)
found_path = @file_finder_helper.find_file_in_collection(header, @configurator.collection_all_headers, :error)
return found_path
end
def find_header_input_for_mock_file(mock_file)
found_path = find_header_file(mock_file)
mock_input = found_path
if (@configurator.project_use_test_preprocessor)
mock_input = @cacheinator.diff_cached_test_file( @file_path_utils.form_preprocessed_file_filepath( found_path ) )
end
return mock_input
end
def find_source_from_test(test, complain)
test_prefix = @configurator.project_test_file_prefix
source_paths = @configurator.collection_all_source
source = File.basename(test).sub(/#{test_prefix}/, '')
# we don't blow up if a test file has no corresponding source file
return @file_finder_helper.find_file_in_collection(source, source_paths, complain)
end
def find_test_from_runner_path(runner_path)
extension_source = @configurator.extension_source
test_file = File.basename(runner_path).sub(/#{@configurator.test_runner_file_suffix}#{'\\'+extension_source}/, extension_source)
found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error)
return found_path
end
def find_test_input_for_runner_file(runner_path)
found_path = find_test_from_runner_path(runner_path)
runner_input = found_path
if (@configurator.project_use_test_preprocessor)
runner_input = @cacheinator.diff_cached_test_file( @file_path_utils.form_preprocessed_file_filepath( found_path ) )
end
return runner_input
end
def find_test_from_file_path(file_path)
test_file = File.basename(file_path).ext(@configurator.extension_source)
found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error)
return found_path
end
def find_test_or_source_or_header_file(file_path)
file = File.basename(file_path)
return @file_finder_helper.find_file_in_collection(file, @all_test_source_and_header_file_collection, :error)
end
def find_compilation_input_file(file_path, complain=:error)
found_file = nil
source_file = File.basename(file_path).ext(@configurator.extension_source)
# We only collect files that already exist when we start up.
# FileLists can produce undesired results for dynamically generated files depending on when they're accessed.
# So collect mocks and runners separately and right now.
if (source_file =~ /#{@configurator.test_runner_file_suffix}/)
found_file =
@file_finder_helper.find_file_in_collection(
source_file,
@file_wrapper.directory_listing( File.join(@configurator.project_test_runners_path, '*') ),
complain)
elsif (@configurator.project_use_mocks and (source_file =~ /#{@configurator.cmock_mock_prefix}/))
found_file =
@file_finder_helper.find_file_in_collection(
source_file,
@file_wrapper.directory_listing( File.join(@configurator.cmock_mock_path, '*') ),
complain)
else
found_file =
@file_finder_helper.find_file_in_collection(
source_file,
@configurator.collection_all_existing_compilation_input,
complain)
end
return found_file
end
def find_source_file(file_path, complain)
source_file = File.basename(file_path).ext(@configurator.extension_source)
return @file_finder_helper.find_file_in_collection(source_file, @configurator.collection_all_source, complain)
end
def find_assembly_file(file_path)
assembly_file = File.basename(file_path).ext(@configurator.extension_assembly)
return @file_finder_helper.find_file_in_collection(assembly_file, @configurator.collection_all_assembly, :error)
end
end

View File

@ -0,0 +1,54 @@
require 'fileutils'
require 'constants' # for Verbosity enumeration
class FileFinderHelper
constructor :streaminator
def find_file_in_collection(file_name, file_list, complain, extra_message="")
file_to_find = nil
file_list.each do |item|
base_file = File.basename(item)
# case insensitive comparison
if (base_file.casecmp(file_name) == 0)
# case sensitive check
if (base_file == file_name)
file_to_find = item
break
else
blow_up(file_name, "However, a filename having different capitalization was found: '#{item}'.")
end
end
end
case (complain)
when :error then blow_up(file_name, extra_message) if (file_to_find.nil?)
when :warn then gripe(file_name, extra_message) if (file_to_find.nil?)
#when :ignore then
end
return file_to_find
end
private
def blow_up(file_name, extra_message="")
error = "ERROR: Found no file '#{file_name}' in search paths."
error += ' ' if (extra_message.length > 0)
@streaminator.stderr_puts(error + extra_message, Verbosity::ERRORS)
raise
end
def gripe(file_name, extra_message="")
warning = "WARNING: Found no file '#{file_name}' in search paths."
warning += ' ' if (extra_message.length > 0)
@streaminator.stderr_puts(warning + extra_message, Verbosity::COMPLAIN)
end
end

View File

@ -0,0 +1,189 @@
require 'rubygems'
require 'rake' # for ext()
require 'fileutils'
require 'system_wrapper'
# global utility methods (for plugins, project files, etc.)
def ceedling_form_filepath(destination_path, original_filepath, new_extension=nil)
filename = File.basename(original_filepath)
filename.replace(filename.ext(new_extension)) if (!new_extension.nil?)
return File.join( destination_path.gsub(/\\/, '/'), filename )
end
class FilePathUtils
GLOB_MATCHER = /[\*\?\{\}\[\]]/
constructor :configurator, :file_wrapper
######### class methods ##########
# standardize path to use '/' path separator & begin with './' & have no trailing path separator
def self.standardize(path)
path.strip!
path.gsub!(/\\/, '/')
path.gsub!(/^((\+|-):)?\.\//, '')
path.chomp!('/')
return path
end
def self.os_executable_ext(executable)
return executable.ext('.exe') if SystemWrapper.windows?
return executable
end
# extract directory path from between optional add/subtract aggregation modifiers and up to glob specifiers
# note: slightly different than File.dirname in that /files/foo remains /files/foo and does not become /files
def self.extract_path(path)
path = path.sub(/^(\+|-):/, '')
# find first occurrence of path separator followed by directory glob specifier: *, ?, {, }, [, ]
find_index = (path =~ GLOB_MATCHER)
# no changes needed (lop off final path separator)
return path.chomp('/') if (find_index.nil?)
# extract up to first glob specifier
path = path[0..(find_index-1)]
# lop off everything up to and including final path separator
find_index = path.rindex('/')
return path[0..(find_index-1)] if (not find_index.nil?)
# return string up to first glob specifier if no path separator found
return path
end
# return whether the given path is to be aggregated (no aggregation modifier defaults to same as +:)
def self.add_path?(path)
return (path =~ /^-:/).nil?
end
# get path (and glob) lopping off optional +: / -: prefixed aggregation modifiers
def self.extract_path_no_aggregation_operators(path)
return path.sub(/^(\+|-):/, '')
end
# all the globs that may be in a path string work fine with one exception;
# to recurse through all subdirectories, the glob is dir/**/** but our paths use
# convention of only dir/**
def self.reform_glob(path)
return path if (path =~ /\/\*\*$/).nil?
return path + '/**'
end
def self.form_ceedling_vendor_path(*filepaths)
return File.join( CEEDLING_VENDOR, filepaths )
end
######### instance methods ##########
def form_temp_path(filepath, prefix='')
return File.join( @configurator.project_temp_path, prefix + File.basename(filepath) )
end
### release ###
def form_release_build_cache_path(filepath)
return File.join( @configurator.project_release_build_cache_path, File.basename(filepath) )
end
def form_release_dependencies_filepath(filepath)
return File.join( @configurator.project_release_dependencies_path, File.basename(filepath).ext(@configurator.extension_dependencies) )
end
def form_release_build_c_object_filepath(filepath)
return File.join( @configurator.project_release_build_output_c_path, File.basename(filepath).ext(@configurator.extension_object) )
end
def form_release_build_asm_object_filepath(filepath)
return File.join( @configurator.project_release_build_output_asm_path, File.basename(filepath).ext(@configurator.extension_object) )
end
def form_release_build_c_objects_filelist(files)
return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_build_output_c_path}/%n#{@configurator.extension_object}")
end
def form_release_build_asm_objects_filelist(files)
return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_build_output_asm_path}/%n#{@configurator.extension_object}")
end
def form_release_build_c_list_filepath(filepath)
return File.join( @configurator.project_release_build_output_c_path, File.basename(filepath).ext(@configurator.extension_list) )
end
def form_release_dependencies_filelist(files)
return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_dependencies_path}/%n#{@configurator.extension_dependencies}")
end
### tests ###
def form_test_build_cache_path(filepath)
return File.join( @configurator.project_test_build_cache_path, File.basename(filepath) )
end
def form_pass_results_filepath(filepath)
return File.join( @configurator.project_test_results_path, File.basename(filepath).ext(@configurator.extension_testpass) )
end
def form_fail_results_filepath(filepath)
return File.join( @configurator.project_test_results_path, File.basename(filepath).ext(@configurator.extension_testfail) )
end
def form_runner_filepath_from_test(filepath)
return File.join( @configurator.project_test_runners_path, File.basename(filepath, @configurator.extension_source)) + @configurator.test_runner_file_suffix + @configurator.extension_source
end
def form_test_filepath_from_runner(filepath)
return filepath.sub(/#{TEST_RUNNER_FILE_SUFFIX}/, '')
end
def form_runner_object_filepath_from_test(filepath)
return (form_test_build_object_filepath(filepath)).sub(/(#{@configurator.extension_object})$/, "#{@configurator.test_runner_file_suffix}\\1")
end
def form_test_build_object_filepath(filepath)
return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_object) )
end
def form_test_executable_filepath(filepath)
return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_executable) )
end
def form_test_build_map_filepath(filepath)
return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_map) )
end
def form_test_build_list_filepath(filepath)
return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_list) )
end
def form_preprocessed_file_filepath(filepath)
return File.join( @configurator.project_test_preprocess_files_path, File.basename(filepath) )
end
def form_preprocessed_includes_list_filepath(filepath)
return File.join( @configurator.project_test_preprocess_includes_path, File.basename(filepath) )
end
def form_test_build_objects_filelist(sources)
return (@file_wrapper.instantiate_file_list(sources)).pathmap("#{@configurator.project_test_build_output_path}/%n#{@configurator.extension_object}")
end
def form_preprocessed_mockable_headers_filelist(mocks)
# pathmapping note: "%{#{@configurator.cmock_mock_prefix},}n" replaces mock_prefix with nothing (signified by absence of anything after comma inside replacement brackets)
return (@file_wrapper.instantiate_file_list(mocks)).pathmap("#{@configurator.project_test_preprocess_files_path}/%{#{@configurator.cmock_mock_prefix},}n#{@configurator.extension_header}")
end
def form_mocks_source_filelist(mocks)
return (@file_wrapper.instantiate_file_list(mocks)).pathmap("#{@configurator.cmock_mock_path}/%n#{@configurator.extension_source}")
end
def form_test_dependencies_filelist(files)
return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_test_dependencies_path}/%n#{@configurator.extension_dependencies}")
end
def form_pass_results_filelist(path, files)
return (@file_wrapper.instantiate_file_list(files)).pathmap("#{path}/%n#{@configurator.extension_testpass}")
end
end

View File

@ -0,0 +1,69 @@
require 'rubygems'
require 'rake'
require 'set'
require 'fileutils'
require 'file_path_utils.rb'
class FileSystemUtils
constructor :file_wrapper
# build up path list from input of one or more strings or arrays of (+/-) paths & globs
def collect_paths(*paths)
raw = [] # all paths and globs
plus = Set.new # all paths to expand and add
minus = Set.new # all paths to remove from plus set
# assemble all globs and simple paths, reforming our glob notation to ruby globs
paths.each do |paths_container|
case (paths_container)
when String then raw << (FilePathUtils::reform_glob(paths_container))
when Array then paths_container.each {|path| raw << (FilePathUtils::reform_glob(path))}
else raise "Don't know how to handle #{paths_container.class}"
end
end
# iterate through each path and glob
raw.each do |path|
dirs = [] # container for only (expanded) paths
# if a glob, expand it and slurp up all non-file paths
if path.include?('*')
# grab base directory only if globs are snug up to final path separator
if (path =~ /\/\*+$/)
dirs << FilePathUtils.extract_path(path)
end
# grab expanded sub-directory globs
expanded = @file_wrapper.directory_listing( FilePathUtils.extract_path_no_aggregation_operators(path) )
expanded.each do |entry|
dirs << entry if @file_wrapper.directory?(entry)
end
# else just grab simple path
# note: we could just run this through glob expansion but such an
# approach doesn't handle a path not yet on disk)
else
dirs << FilePathUtils.extract_path_no_aggregation_operators(path)
end
# add dirs to the appropriate set based on path aggregation modifier if present
FilePathUtils.add_path?(path) ? plus.merge(dirs) : minus.merge(dirs)
end
return (plus - minus).to_a.uniq
end
# given a file list, add to it or remove from it
def revise_file_list(list, revisions)
revisions.each do |revision|
# include or exclude file or glob to file list
file = FilePathUtils.extract_path_no_aggregation_operators( revision )
FilePathUtils.add_path?(revision) ? list.include(file) : list.exclude(file)
end
end
end

View File

@ -0,0 +1,10 @@
class FileSystemWrapper
def cd(path)
FileUtils.cd path do
yield
end
end
end

View File

@ -0,0 +1,79 @@
require 'rubygems'
require 'rake' # for FileList
require 'constants'
require 'fileutils'
class FileWrapper
def get_expanded_path(path)
return File.expand_path(path)
end
def basename(path, extension=nil)
return File.basename(path, extension) if extension
return File.basename(path)
end
def exist?(filepath)
return true if (filepath == NULL_FILE_PATH)
return File.exist?(filepath)
end
def directory?(path)
return File.directory?(path)
end
def dirname(path)
return File.dirname(path)
end
def directory_listing(glob)
return Dir.glob(glob)
end
def rm_f(filepath, options={})
FileUtils.rm_f(filepath, options)
end
def rm_r(filepath, options={})
FileUtils.rm_r(filepath, options={})
end
def cp(source, destination, options={})
FileUtils.cp(source, destination, options)
end
def compare(from, to)
return FileUtils.compare_file(from, to)
end
def open(filepath, flags)
File.open(filepath, flags) do |file|
yield(file)
end
end
def read(filepath)
return File.read(filepath)
end
def touch(filepath, options={})
FileUtils.touch(filepath, options)
end
def write(filepath, contents, flags='w')
File.open(filepath, flags) do |file|
file.write(contents)
end
end
def readlines(filepath)
return File.readlines(filepath)
end
def instantiate_file_list(files=[])
return FileList.new(files)
end
end

54
tests/vendor/ceedling/lib/flaginator.rb vendored Normal file
View File

@ -0,0 +1,54 @@
require 'rubygems'
require 'rake' # for ext()
require 'fileutils'
require 'constants'
# :flags:
# :release:
# :compile:
# :*: # add '-foo' to compilation of all files not main.c
# - -foo
# :main: # add '-Wall' to compilation of main.c
# - -Wall
# :test:
# :link:
# :test_main: # add '--bar --baz' to linking of test_main.exe
# - --bar
# - --baz
class Flaginator
constructor :configurator
def flag_down( operation, context, file )
# create configurator accessor method
accessor = ('flags_' + context.to_s).to_sym
# create simple filename key from whatever filename provided
file_key = File.basename( file ).ext('').to_sym
# if no entry in configuration for flags for this context, bail out
return [] if not @configurator.respond_to?( accessor )
# get flags sub hash associated with this context
flags = @configurator.send( accessor )
# if operation not represented in flags hash, bail out
return [] if not flags.include?( operation )
# redefine flags to sub hash associated with the operation
flags = flags[operation]
# if our file is in the flags hash, extract the array of flags
if (flags.include?( file_key )) then return flags[file_key]
# if our file isn't in the flags hash, but there is default for all other files, extract array of flags
elsif (flags.include?( :* )) then return flags[:*]
end
# fall through: flags were specified but none applying to present file
return []
end
end

164
tests/vendor/ceedling/lib/generator.rb vendored Normal file
View File

@ -0,0 +1,164 @@
require 'constants'
class Generator
constructor :configurator,
:generator_helper,
:preprocessinator,
:cmock_builder,
:generator_test_runner,
:generator_test_results,
:flaginator,
:test_includes_extractor,
:tool_executor,
:file_finder,
:file_path_utils,
:streaminator,
:plugin_manager,
:file_wrapper
def generate_shallow_includes_list(context, file)
@preprocessinator.preprocess_shallow_includes(file)
end
def generate_preprocessed_file(context, file)
@streaminator.stdout_puts("Preprocessing #{File.basename(file)}...", Verbosity::NORMAL)
@preprocessinator.preprocess_file(file)
end
def generate_dependencies_file(tool, context, source, object, dependencies)
@streaminator.stdout_puts("Generating dependencies for #{File.basename(source)}...", Verbosity::NORMAL)
command =
@tool_executor.build_command_line(
tool,
source,
dependencies,
object)
@tool_executor.exec( command[:line], command[:options] )
end
def generate_mock(context, header_filepath)
arg_hash = {:header_file => header_filepath, :context => context}
@plugin_manager.pre_mock_generate( arg_hash )
begin
@cmock_builder.cmock.setup_mocks( arg_hash[:header_file] )
rescue
raise
ensure
@plugin_manager.post_mock_generate( arg_hash )
end
end
# test_filepath may be either preprocessed test file or original test file
def generate_test_runner(context, test_filepath, runner_filepath)
arg_hash = {:context => context, :test_file => test_filepath, :runner_file => runner_filepath}
@plugin_manager.pre_runner_generate(arg_hash)
# collect info we need
module_name = File.basename(arg_hash[:test_file])
test_cases = @generator_test_runner.find_test_cases( @file_finder.find_test_from_runner_path(runner_filepath) )
mock_list = @test_includes_extractor.lookup_raw_mock_list(arg_hash[:test_file])
@streaminator.stdout_puts("Generating runner for #{module_name}...", Verbosity::NORMAL)
# build runner file
begin
@generator_test_runner.generate(module_name, runner_filepath, test_cases, mock_list)
rescue
raise
ensure
@plugin_manager.post_runner_generate(arg_hash)
end
end
def generate_object_file(tool, context, source, object, list='')
shell_result = {}
arg_hash = {:tool => tool, :context => context, :source => source, :object => object, :list => list}
@plugin_manager.pre_compile_execute(arg_hash)
@streaminator.stdout_puts("Compiling #{File.basename(arg_hash[:source])}...", Verbosity::NORMAL)
command =
@tool_executor.build_command_line( arg_hash[:tool],
arg_hash[:source],
arg_hash[:object],
arg_hash[:list],
@flaginator.flag_down( OPERATION_COMPILE_SYM, context, source ) )
begin
shell_result = @tool_executor.exec( command[:line], command[:options] )
rescue ShellExecutionException => ex
shell_result = ex.shell_result
raise ''
ensure
arg_hash[:shell_result] = shell_result
@plugin_manager.post_compile_execute(arg_hash)
end
end
def generate_executable_file(tool, context, objects, executable, map='')
shell_result = {}
arg_hash = {:tool => tool, :context => context, :objects => objects, :executable => executable, :map => map}
@plugin_manager.pre_link_execute(arg_hash)
@streaminator.stdout_puts("Linking #{File.basename(arg_hash[:executable])}...", Verbosity::NORMAL)
command =
@tool_executor.build_command_line( arg_hash[:tool],
arg_hash[:objects],
arg_hash[:executable],
arg_hash[:map],
@flaginator.flag_down( OPERATION_LINK_SYM, context, executable ) )
begin
shell_result = @tool_executor.exec( command[:line], command[:options] )
rescue ShellExecutionException => ex
notice = "\n" +
"NOTICE: If the linker reports missing symbols, the following may be to blame:\n" +
" 1. Test lacks #include statements corresponding to needed source files.\n" +
" 2. Project search paths do not contain source files corresponding to #include statements in the test.\n"
if (@configurator.project_use_mocks)
notice += " 3. Test does not #include needed mocks.\n\n"
else
notice += "\n"
end
@streaminator.stderr_puts(notice, Verbosity::COMPLAIN)
shell_result = ex.shell_result
raise ''
ensure
arg_hash[:shell_result] = shell_result
@plugin_manager.post_link_execute(arg_hash)
end
end
def generate_test_results(tool, context, executable, result)
arg_hash = {:tool => tool, :context => context, :executable => executable, :result_file => result}
@plugin_manager.pre_test_fixture_execute(arg_hash)
@streaminator.stdout_puts("Running #{File.basename(arg_hash[:executable])}...", Verbosity::NORMAL)
# Unity's exit code is equivalent to the number of failed tests, so we tell @tool_executor not to fail out if there are failures
# so that we can run all tests and collect all results
command = @tool_executor.build_command_line(arg_hash[:tool], arg_hash[:executable])
command[:options][:boom] = false
shell_result = @tool_executor.exec( command[:line], command[:options] )
@generator_helper.test_results_error_handler(executable, shell_result)
processed = @generator_test_results.process_and_write_results( shell_result,
arg_hash[:result_file],
@file_finder.find_test_from_file_path(arg_hash[:executable]) )
arg_hash[:result_file] = processed[:result_file]
arg_hash[:results] = processed[:results]
arg_hash[:shell_result] = shell_result # for raw output display if no plugins for formatted display
@plugin_manager.post_test_fixture_execute(arg_hash)
end
end

View File

@ -0,0 +1,40 @@
require 'constants'
class GeneratorHelper
constructor :streaminator
def test_results_error_handler(executable, shell_result)
notice = ''
error = false
if (shell_result[:output].nil? or shell_result[:output].strip.empty?)
error = true
# mirror style of generic tool_executor failure output
notice = "\n" +
"ERROR: Test executable \"#{File.basename(executable)}\" failed.\n" +
"> Produced no output to $stdout.\n"
elsif ((shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN).nil?)
error = true
# mirror style of generic tool_executor failure output
notice = "\n" +
"ERROR: Test executable \"#{File.basename(executable)}\" failed.\n" +
"> Produced no final test result counts in $stdout:\n" +
"#{shell_result[:output].strip}\n"
end
if (error)
# since we told the tool executor to ignore the exit code, handle it explicitly here
notice += "> And exited with status: [#{shell_result[:exit_code]}] (count of failed tests).\n" if (shell_result[:exit_code] != nil)
notice += "> And then likely crashed.\n" if (shell_result[:exit_code] == nil)
notice += "> This is often a symptom of a bad memory access in source or test code.\n\n"
@streaminator.stderr_puts(notice, Verbosity::COMPLAIN)
raise
end
end
end

View File

@ -0,0 +1,89 @@
require 'rubygems'
require 'rake' # for .ext()
require 'constants'
class GeneratorTestResults
constructor :configurator, :generator_test_results_sanity_checker, :yaml_wrapper
def process_and_write_results(unity_shell_result, results_file, test_file)
output_file = results_file
results = get_results_structure
results[:source][:path] = File.dirname(test_file)
results[:source][:file] = File.basename(test_file)
# process test statistics
if (unity_shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN)
results[:counts][:total] = $1.to_i
results[:counts][:failed] = $2.to_i
results[:counts][:ignored] = $3.to_i
results[:counts][:passed] = (results[:counts][:total] - results[:counts][:failed] - results[:counts][:ignored])
end
# remove test statistics lines
output_string = unity_shell_result[:output].sub(TEST_STDOUT_STATISTICS_PATTERN, '')
# bust up the output into individual lines
raw_unity_lines = output_string.split(/\n|\r\n/)
raw_unity_lines.each do |line|
# process unity output
case line
when /(:IGNORE)/
elements = extract_line_elements(line, results[:source][:file])
results[:ignores] << elements[0]
results[:stdout] << elements[1] if (!elements[1].nil?)
when /(:PASS$)/
elements = extract_line_elements(line, results[:source][:file])
results[:successes] << elements[0]
results[:stdout] << elements[1] if (!elements[1].nil?)
when /(:FAIL)/
elements = extract_line_elements(line, results[:source][:file])
results[:failures] << elements[0]
results[:stdout] << elements[1] if (!elements[1].nil?)
else # collect up all other
results[:stdout] << line.chomp
end
end
@generator_test_results_sanity_checker.verify(results, unity_shell_result[:exit_code])
output_file = results_file.ext(@configurator.extension_testfail) if (results[:counts][:failed] > 0)
@yaml_wrapper.dump(output_file, results)
return { :result_file => output_file, :result => results }
end
private
def get_results_structure
return {
:source => {:path => '', :file => ''},
:successes => [],
:failures => [],
:ignores => [],
:counts => {:total => 0, :passed => 0, :failed => 0, :ignored => 0},
:stdout => [],
}
end
def extract_line_elements(line, filename)
# handle anything preceding filename in line as extra output to be collected
stdout = nil
stdout_regex = /(.+)#{Regexp.escape(filename)}.+/i
if (line =~ stdout_regex)
stdout = $1.clone
line.sub!(/#{Regexp.escape(stdout)}/, '')
end
# collect up test results minus and extra output
elements = (line.strip.split(':'))[1..-1]
return {:test => elements[1], :line => elements[0].to_i, :message => (elements[3..-1].join(':')).strip}, stdout
end
end

View File

@ -0,0 +1,62 @@
require 'constants'
require 'rubygems'
require 'rake' # for ext() method
class GeneratorTestResultsSanityChecker
constructor :configurator, :streaminator
def verify(results, unity_exit_code)
# do no sanity checking if it's disabled
return if (@configurator.sanity_checks == TestResultsSanityChecks::NONE)
ceedling_ignores_count = results[:ignores].size
ceedling_failures_count = results[:failures].size
ceedling_tests_summation = (ceedling_ignores_count + ceedling_failures_count + results[:successes].size)
# Exit code handling is not a sanity check that can always be performed because
# command line simulators may or may not pass through Unity's exit code
if (@configurator.sanity_checks >= TestResultsSanityChecks::THOROUGH)
# many platforms limit exit codes to a maximum of 255
if ((ceedling_failures_count != unity_exit_code) and (unity_exit_code < 255))
sanity_check_warning(results[:source][:file], "Unity's exit code (#{unity_exit_code}) does not match Ceedling's summation of failed test cases (#{ceedling_failures_count}).")
end
if ((ceedling_failures_count < 255) and (unity_exit_code == 255))
sanity_check_warning(results[:source][:file], "Ceedling's summation of failed test cases (#{ceedling_failures_count}) is less than Unity's exit code (255 or more).")
end
end
if (ceedling_ignores_count != results[:counts][:ignored])
sanity_check_warning(results[:source][:file], "Unity's final ignore count (#{results[:counts][:ignored]}) does not match Ceedling's summation of ignored test cases (#{ceedling_ignores_count}).")
end
if (ceedling_failures_count != results[:counts][:failed])
sanity_check_warning(results[:source][:file], "Unity's final fail count (#{results[:counts][:failed]}) does not match Ceedling's summation of failed test cases (#{ceedling_failures_count}).")
end
if (ceedling_tests_summation != results[:counts][:total])
sanity_check_warning(results[:source][:file], "Unity's final test count (#{results[:counts][:total]}) does not match Ceedling's summation of all test cases (#{ceedling_tests_summation}).")
end
end
private
def sanity_check_warning(file, message)
notice = "\n" +
"ERROR: Internal sanity check for test fixture '#{file.ext(@configurator.extension_executable)}' finds that #{message}\n" +
" Possible causes:\n" +
" 1. Your test + source dereferenced a null pointer.\n" +
" 2. Your test + source indexed past the end of a buffer.\n" +
" 3. Your test + source committed a memory access violation.\n" +
" 4. Your test fixture produced an exit code of 0 despite execution ending prematurely.\n" +
" Sanity check failures of test results are usually a symptom of interrupted test execution.\n\n"
@streaminator.stderr_puts( notice )
raise
end
end

View File

@ -0,0 +1,63 @@
class GeneratorTestRunner
constructor :configurator, :file_path_utils, :file_wrapper
def find_test_cases(test_file)
tests = []
tests_and_line_numbers = []
lines = []
# if we don't have preprocessor assistance, do some basic preprocessing of our own
if (not @configurator.project_use_test_preprocessor)
source = @file_wrapper.read(test_file)
# remove line comments
source = source.gsub(/\/\/.*$/, '')
# remove block comments
source = source.gsub(/\/\*.*?\*\//m, '')
# treat preprocessor directives as a logical line
lines = source.split(/(^\s*\#.*$) | (;|\{|\}) /x) # match ;, {, and } as end of lines
# otherwise, read the preprocessed file raw
else
lines = @file_wrapper.read( @file_path_utils.form_preprocessed_file_filepath(test_file) ).split(/;|\{|\}/)
end
# step 1. find test functions in (possibly preprocessed) file
# (note that lines are not broken up at end of lines)
lines.each do |line|
if (line =~ /^\s*void\s+((T|t)est.*)\s*\(\s*(void)?\s*\)/m)
tests << ($1.strip)
end
end
# step 2. associate test functions with line numbers in (non-preprocessed) original file
# (note that this time we must scan file contents broken up by end of lines)
raw_lines = @file_wrapper.read(test_file).split("\n")
raw_index = 0
tests.each do |test|
raw_lines[raw_index..-1].each_with_index do |line, index|
# test function might be declared across lines; look for it by its name followed
# by a few tell-tale signs
if (line =~ /#{test}\s*($|\(|\()/)
raw_index += (index + 1)
tests_and_line_numbers << {:test => test, :line_number => raw_index}
break
end
end
end
return tests_and_line_numbers
end
def generate(module_name, runner_filepath, test_cases, mock_list)
require 'generate_test_runner.rb'
@test_runner_generator ||= UnityTestRunnerGenerator.new( @configurator.get_runner_config )
@test_runner_generator.generate( module_name,
runner_filepath,
test_cases,
mock_list)
end
end

31
tests/vendor/ceedling/lib/loginator.rb vendored Normal file
View File

@ -0,0 +1,31 @@
class Loginator
constructor :configurator, :project_file_loader, :project_config_manager, :file_wrapper, :system_wrapper
def setup_log_filepath
config_files = []
config_files << @project_file_loader.main_file
config_files << @project_file_loader.user_file
config_files.concat( @project_config_manager.options_files )
config_files.compact!
config_files.map! { |file| file.ext('') }
log_name = config_files.join( '_' )
@project_log_filepath = File.join( @configurator.project_log_path, log_name.ext('.log') )
end
def log(string, heading=nil)
return if (not @configurator.project_logging)
output = "\n[#{@system_wrapper.time_now}]"
output += " :: #{heading}" if (not heading.nil?)
output += "\n#{string.strip}\n"
@file_wrapper.write(@project_log_filepath, output, 'a')
end
end

46
tests/vendor/ceedling/lib/makefile.rb vendored Normal file
View File

@ -0,0 +1,46 @@
# modified version of Rake's provided make-style dependency loader
# customizations:
# (1) handles windows drives in paths -- colons don't confuse task demarcation
# (2) handles spaces in directory paths
module Rake
# Makefile loader to be used with the import file loader.
class MakefileLoader
# Load the makefile dependencies in +fn+.
def load(fn)
open(fn) do |mf|
lines = mf.read
lines.gsub!(/#[^\n]*\n/m, "") # remove comments
lines.gsub!(/\\\n/, ' ') # string together line continuations into single line
lines.split("\n").each do |line|
process_line(line)
end
end
end
private
# Process one logical line of makefile data.
def process_line(line)
# split on presence of task demaractor followed by space (i.e don't get confused by a colon in a win path)
file_tasks, args = line.split(/:\s/)
return if args.nil?
# split at non-escaped space boundary between files (i.e. escaped spaces in paths are left alone)
dependents = args.split(/\b\s+/)
# replace escaped spaces and clean up any extra whitespace
dependents.map! { |path| path.gsub(/\\ /, ' ').strip }
file_tasks.strip.split.each do |file_task|
file file_task => dependents
end
end
end
# Install the handler
Rake.application.add_loader('mf', MakefileLoader.new)
end

307
tests/vendor/ceedling/lib/objects.yml vendored Normal file
View File

@ -0,0 +1,307 @@
file_wrapper:
file_system_wrapper:
stream_wrapper:
rake_wrapper:
yaml_wrapper:
system_wrapper:
cmock_builder:
reportinator:
rake_utils:
compose:
- rake_wrapper
system_utils:
compose:
- system_wrapper
file_path_utils:
compose:
- configurator
- file_wrapper
file_system_utils:
compose: file_wrapper
project_file_loader:
compose:
- yaml_wrapper
- stream_wrapper
- system_wrapper
- file_wrapper
project_config_manager:
compose:
- cacheinator
- yaml_wrapper
cacheinator:
compose:
- cacheinator_helper
- file_path_utils
- file_wrapper
- yaml_wrapper
cacheinator_helper:
compose:
- file_wrapper
- yaml_wrapper
tool_executor:
compose:
- configurator
- tool_executor_helper
- streaminator
- system_wrapper
tool_executor_helper:
compose:
- streaminator
- system_utils
- system_wrapper
configurator:
compose:
- configurator_setup
- configurator_plugins
- configurator_builder
- cmock_builder
- yaml_wrapper
- system_wrapper
configurator_setup:
compose:
- configurator_builder
- configurator_validator
- configurator_plugins
- stream_wrapper
configurator_plugins:
compose:
- stream_wrapper
- file_wrapper
- system_wrapper
configurator_validator:
compose:
- file_wrapper
- stream_wrapper
- system_wrapper
configurator_builder:
compose:
- file_system_utils
- file_wrapper
- system_wrapper
loginator:
compose:
- configurator
- project_file_loader
- project_config_manager
- file_wrapper
- system_wrapper
streaminator:
compose:
- streaminator_helper
- verbosinator
- loginator
- stream_wrapper
streaminator_helper:
setupinator:
plugin_builder:
plugin_manager:
compose:
- configurator
- plugin_manager_helper
- streaminator
- reportinator
- system_wrapper
plugin_manager_helper:
plugin_reportinator:
compose:
- plugin_reportinator_helper
- plugin_manager
- reportinator
plugin_reportinator_helper:
compose:
- configurator
- streaminator
- yaml_wrapper
- file_wrapper
verbosinator:
compose: configurator
file_finder:
compose:
- configurator
- file_finder_helper
- cacheinator
- file_path_utils
- file_wrapper
- yaml_wrapper
file_finder_helper:
compose: streaminator
test_includes_extractor:
compose:
- configurator
- yaml_wrapper
- file_wrapper
task_invoker:
compose:
- dependinator
- rake_utils
- rake_wrapper
flaginator:
compose:
- configurator
generator:
compose:
- configurator
- generator_helper
- preprocessinator
- cmock_builder
- generator_test_runner
- generator_test_results
- flaginator
- test_includes_extractor
- tool_executor
- file_finder
- file_path_utils
- streaminator
- plugin_manager
- file_wrapper
generator_helper:
compose:
- streaminator
generator_test_results:
compose:
- configurator
- generator_test_results_sanity_checker
- yaml_wrapper
generator_test_results_sanity_checker:
compose:
- configurator
- streaminator
generator_test_runner:
compose:
- configurator
- file_path_utils
- file_wrapper
dependinator:
compose:
- configurator
- project_config_manager
- test_includes_extractor
- file_path_utils
- rake_wrapper
- file_wrapper
preprocessinator:
compose:
- preprocessinator_helper
- preprocessinator_includes_handler
- preprocessinator_file_handler
- task_invoker
- file_path_utils
- yaml_wrapper
preprocessinator_helper:
compose:
- configurator
- test_includes_extractor
- task_invoker
- file_finder
- file_path_utils
preprocessinator_includes_handler:
compose:
- configurator
- tool_executor
- task_invoker
- file_path_utils
- yaml_wrapper
- file_wrapper
preprocessinator_file_handler:
compose:
- preprocessinator_extractor
- configurator
- tool_executor
- file_path_utils
- file_wrapper
preprocessinator_extractor:
test_invoker:
compose:
- configurator
- test_invoker_helper
- plugin_manager
- streaminator
- preprocessinator
- task_invoker
- dependinator
- project_config_manager
- build_invoker_utils
- file_path_utils
- file_wrapper
test_invoker_helper:
compose:
- configurator
- task_invoker
- test_includes_extractor
- file_finder
- file_path_utils
- file_wrapper
release_invoker:
compose:
- configurator
- release_invoker_helper
- build_invoker_utils
- dependinator
- task_invoker
- file_path_utils
- file_wrapper
release_invoker_helper:
compose:
- configurator
- dependinator
- task_invoker
build_invoker_utils:
compose:
- configurator
- streaminator
erb_wrapper:

19
tests/vendor/ceedling/lib/par_map.rb vendored Normal file
View File

@ -0,0 +1,19 @@
def par_map(n, things, &block)
queue = Queue.new
things.each { |thing| queue << thing }
threads = (1..n).collect do
Thread.new do
begin
while true
yield queue.pop(true)
end
rescue ThreadError
end
end
end
threads.each { |t| t.join }
end

80
tests/vendor/ceedling/lib/plugin.rb vendored Normal file
View File

@ -0,0 +1,80 @@
class String
# reformat a multiline string to have given number of whitespace columns;
# helpful for formatting heredocs
def left_margin(margin=0)
non_whitespace_column = 0
new_lines = []
# find first line with non-whitespace and count left columns of whitespace
self.each_line do |line|
if (line =~ /^\s*\S/)
non_whitespace_column = $&.length - 1
break
end
end
# iterate through each line, chopping off leftmost whitespace columns and add back the desired whitespace margin
self.each_line do |line|
columns = []
margin.times{columns << ' '}
# handle special case of line being narrower than width to be lopped off
if (non_whitespace_column < line.length)
new_lines << "#{columns.join}#{line[non_whitespace_column..-1]}"
else
new_lines << "\n"
end
end
return new_lines.join
end
end
class Plugin
attr_reader :name, :environment
attr_accessor :plugin_objects
def initialize(system_objects, name)
@environment = []
@ceedling = system_objects
@name = name
self.setup
end
def setup; end
# mock generation
def pre_mock_generate(arg_hash); end
def post_mock_generate(arg_hash); end
# test runner generation
def pre_runner_generate(arg_hash); end
def post_runner_generate(arg_hash); end
# compilation (test or source)
def pre_compile_execute(arg_hash); end
def post_compile_execute(arg_hash); end
# linking (test or source)
def pre_link_execute(arg_hash); end
def post_link_execute(arg_hash); end
# test fixture execution
def pre_test_fixture_execute(arg_hash); end
def post_test_fixture_execute(arg_hash); end
# test task
def pre_test; end
def post_test; end
# release task
def pre_release; end
def post_release; end
# whole shebang (any use of Ceedling)
def pre_build; end
def post_build; end
def summary; end
end

View File

@ -0,0 +1,53 @@
require 'plugin'
class PluginBuilder
attr_accessor :plugin_objects
def construct_plugin(plugin_name, object_map_yaml, system_objects)
# @streaminator.stdout_puts("Constructing plugin #{plugin_name}...", Verbosity::OBNOXIOUS)
object_map = {}
@plugin_objects = {}
@system_objects = system_objects
if object_map_yaml
@object_map = YAML.load(object_map_yaml)
@object_map.each_key do |obj|
construct_object(obj)
end
else
raise "Invalid object map for plugin #{plugin_name}!"
end
return @plugin_objects
end
private
def camelize(underscored_name)
return underscored_name.gsub(/(_|^)([a-z0-9])/) {$2.upcase}
end
def construct_object(obj)
if @plugin_objects[obj].nil?
if @object_map[obj] && @object_map[obj]['compose']
@object_map[obj]['compose'].each do |dep|
construct_object(dep)
end
end
build_object(obj)
end
end
def build_object(new_object)
if @plugin_objects[new_object.to_sym].nil?
# @streaminator.stdout_puts("Building plugin object #{new_object}", Verbosity::OBNOXIOUS)
require new_object
class_name = camelize(new_object)
new_instance = eval("#{class_name}.new(@system_objects, class_name.to_s)")
new_instance.plugin_objects = @plugin_objects
@plugin_objects[new_object.to_sym] = new_instance
end
end
end

View File

@ -0,0 +1,107 @@
require 'constants'
require 'set'
class PluginManager
constructor :configurator, :plugin_manager_helper, :streaminator, :reportinator, :system_wrapper
def setup
@build_fail_registry = []
@plugin_objects = [] # so we can preserve order
end
def load_plugin_scripts(script_plugins, system_objects)
environment = []
script_plugins.each do |plugin|
# protect against instantiating object multiple times due to processing config multiple times (option files, etc)
next if (@plugin_manager_helper.include?(@plugin_objects, plugin))
begin
@system_wrapper.require_file( "#{plugin}.rb" )
object = @plugin_manager_helper.instantiate_plugin_script( camelize(plugin), system_objects, plugin )
@plugin_objects << object
environment += object.environment
# add plugins to hash of all system objects
system_objects[plugin.downcase.to_sym] = object
rescue
puts "Exception raised while trying to load plugin: #{plugin}"
raise
end
end
yield( { :environment => environment } ) if (environment.size > 0)
end
def plugins_failed?
return (@build_fail_registry.size > 0)
end
def print_plugin_failures
if (@build_fail_registry.size > 0)
report = @reportinator.generate_banner('BUILD FAILURE SUMMARY')
@build_fail_registry.each do |failure|
report += "#{' - ' if (@build_fail_registry.size > 1)}#{failure}\n"
end
report += "\n"
@streaminator.stderr_puts(report, Verbosity::ERRORS)
end
end
def register_build_failure(message)
@build_fail_registry << message if (message and not message.empty?)
end
#### execute all plugin methods ####
def pre_mock_generate(arg_hash); execute_plugins(:pre_mock_generate, arg_hash); end
def post_mock_generate(arg_hash); execute_plugins(:post_mock_generate, arg_hash); end
def pre_runner_generate(arg_hash); execute_plugins(:pre_runner_generate, arg_hash); end
def post_runner_generate(arg_hash); execute_plugins(:post_runner_generate, arg_hash); end
def pre_compile_execute(arg_hash); execute_plugins(:pre_compile_execute, arg_hash); end
def post_compile_execute(arg_hash); execute_plugins(:post_compile_execute, arg_hash); end
def pre_link_execute(arg_hash); execute_plugins(:pre_link_execute, arg_hash); end
def post_link_execute(arg_hash); execute_plugins(:post_link_execute, arg_hash); end
def pre_test_fixture_execute(arg_hash); execute_plugins(:pre_test_fixture_execute, arg_hash); end
def post_test_fixture_execute(arg_hash)
# special arbitration: raw test results are printed or taken over by plugins handling the job
@streaminator.stdout_puts(arg_hash[:shell_result][:output]) if (@configurator.plugins_display_raw_test_results)
execute_plugins(:post_test_fixture_execute, arg_hash)
end
def pre_test; execute_plugins(:pre_test); end
def post_test; execute_plugins(:post_test); end
def pre_release; execute_plugins(:pre_release); end
def post_release; execute_plugins(:post_release); end
def pre_build; execute_plugins(:pre_build); end
def post_build; execute_plugins(:post_build); end
def summary; execute_plugins(:summary); end
private ####################################
def camelize(underscored_name)
return underscored_name.gsub(/(_|^)([a-z0-9])/) {$2.upcase}
end
def execute_plugins(method, *args)
@plugin_objects.each do |plugin|
begin
plugin.send(method, *args)
rescue
puts "Exception raised in plugin: #{plugin.name}, in method #{method}"
raise
end
end
end
end

View File

@ -0,0 +1,19 @@
class PluginManagerHelper
def include?(plugins, name)
include = false
plugins.each do |plugin|
if (plugin.name == name)
include = true
break
end
end
return include
end
def instantiate_plugin_script(plugin, system_objects, name)
return eval("#{plugin}.new(system_objects, name)")
end
end

View File

@ -0,0 +1,75 @@
require 'constants'
require 'defaults'
class PluginReportinator
constructor :plugin_reportinator_helper, :plugin_manager, :reportinator
def setup
@test_results_template = nil
end
def set_system_objects(system_objects)
@plugin_reportinator_helper.ceedling = system_objects
end
def fetch_results(results_path, test, options={:boom => false})
return @plugin_reportinator_helper.fetch_results( File.join(results_path, test), options )
end
def generate_banner(message)
return @reportinator.generate_banner(message)
end
def assemble_test_results(results_list, options={:boom => false})
aggregated_results = get_results_structure
results_list.each do |result_path|
results = @plugin_reportinator_helper.fetch_results( result_path, options )
@plugin_reportinator_helper.process_results(aggregated_results, results)
end
return aggregated_results
end
def register_test_results_template(template)
@test_results_template = template if (@test_results_template.nil?)
end
def run_test_results_report(hash, verbosity=Verbosity::NORMAL, &block)
run_report( $stdout,
((@test_results_template.nil?) ? DEFAULT_TESTS_RESULTS_REPORT_TEMPLATE : @test_results_template),
hash,
verbosity,
&block )
end
def run_report(stream, template, hash=nil, verbosity=Verbosity::NORMAL)
failure = nil
failure = yield() if block_given?
@plugin_manager.register_build_failure( failure )
@plugin_reportinator_helper.run_report( stream, template, hash, verbosity )
end
private ###############################
def get_results_structure
return {
:successes => [],
:failures => [],
:ignores => [],
:stdout => [],
:counts => {:total => 0, :passed => 0, :failed => 0, :ignored => 0, :stdout => 0}
}
end
end

View File

@ -0,0 +1,52 @@
require 'constants'
require 'erb'
require 'rubygems'
require 'rake' # for ext()
class PluginReportinatorHelper
attr_writer :ceedling
constructor :configurator, :streaminator, :yaml_wrapper, :file_wrapper
def fetch_results(results_path, options)
pass_path = File.join(results_path.ext( @configurator.extension_testpass ))
fail_path = File.join(results_path.ext( @configurator.extension_testfail ))
if (@file_wrapper.exist?(fail_path))
return @yaml_wrapper.load(fail_path)
elsif (@file_wrapper.exist?(pass_path))
return @yaml_wrapper.load(pass_path)
else
if (options[:boom])
@streaminator.stderr_puts("Could find no test results for '#{File.basename(results_path).ext(@configurator.extension_source)}'", Verbosity::ERRORS)
raise
end
end
return {}
end
def process_results(aggregate_results, results)
return if (results.empty?)
aggregate_results[:successes] << { :source => results[:source].clone, :collection => results[:successes].clone } if (results[:successes].size > 0)
aggregate_results[:failures] << { :source => results[:source].clone, :collection => results[:failures].clone } if (results[:failures].size > 0)
aggregate_results[:ignores] << { :source => results[:source].clone, :collection => results[:ignores].clone } if (results[:ignores].size > 0)
aggregate_results[:stdout] << { :source => results[:source].clone, :collection => results[:stdout].clone } if (results[:stdout].size > 0)
aggregate_results[:counts][:total] += results[:counts][:total]
aggregate_results[:counts][:passed] += results[:counts][:passed]
aggregate_results[:counts][:failed] += results[:counts][:failed]
aggregate_results[:counts][:ignored] += results[:counts][:ignored]
aggregate_results[:counts][:stdout] += results[:stdout].size
end
def run_report(stream, template, hash, verbosity)
output = ERB.new(template, 0, "%<>")
@streaminator.stream_puts(stream, output.result(binding()), verbosity)
end
end

View File

@ -0,0 +1,43 @@
class Preprocessinator
attr_reader :preprocess_file_proc
constructor :preprocessinator_helper, :preprocessinator_includes_handler, :preprocessinator_file_handler, :task_invoker, :file_path_utils, :yaml_wrapper
def setup
# fashion ourselves callbacks @preprocessinator_helper can use
@preprocess_includes_proc = Proc.new { |filepath| self.preprocess_shallow_includes(filepath) }
@preprocess_file_proc = Proc.new { |filepath| self.preprocess_file(filepath) }
end
def preprocess_test_and_invoke_test_mocks(test)
@preprocessinator_helper.preprocess_includes(test, @preprocess_includes_proc)
mocks_list = @preprocessinator_helper.assemble_mocks_list(test)
@preprocessinator_helper.preprocess_mockable_headers(mocks_list, @preprocess_file_proc)
@task_invoker.invoke_test_mocks(mocks_list)
@preprocessinator_helper.preprocess_test_file(test, @preprocess_file_proc)
return mocks_list
end
def preprocess_shallow_includes(filepath)
dependencies_rule = @preprocessinator_includes_handler.form_shallow_dependencies_rule(filepath)
includes = @preprocessinator_includes_handler.extract_shallow_includes(dependencies_rule)
@preprocessinator_includes_handler.write_shallow_includes_list(
@file_path_utils.form_preprocessed_includes_list_filepath(filepath), includes)
end
def preprocess_file(filepath)
@preprocessinator_includes_handler.invoke_shallow_includes_list(filepath)
@preprocessinator_file_handler.preprocess_file( filepath, @yaml_wrapper.load(@file_path_utils.form_preprocessed_includes_list_filepath(filepath)) )
end
end

View File

@ -0,0 +1,30 @@
class PreprocessinatorExtractor
def extract_base_file_from_preprocessed_expansion(filepath)
# preprocessing by way of toolchain preprocessor expands macros, eliminates
# comments, strips out #ifdef code, etc. however, it also expands in place
# each #include'd file. so, we must extract only the lines of the file
# that belong to the file originally preprocessed
# iterate through all lines and alternate between extract and ignore modes
# all lines between a '#'line containing file name of our filepath and the
# next '#'line should be extracted
base_name = File.basename(filepath)
not_pragma = /^#(?!pragma\b)/ # preprocessor directive that's not a #pragma
pattern = /^#.*(\s|\/|\\|\")#{Regexp.escape(base_name)}/
found_file = false # have we found the file we care about?
lines = []
File.readlines(filepath).each do |line|
if found_file and not line.match(not_pragma)
lines << line
else
found_file = false
end
found_file = true if line.match(pattern)
end
return lines
end
end

View File

@ -0,0 +1,21 @@
class PreprocessinatorFileHandler
constructor :preprocessinator_extractor, :configurator, :tool_executor, :file_path_utils, :file_wrapper
def preprocess_file(filepath, includes)
preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath(filepath)
command = @tool_executor.build_command_line(@configurator.tools_test_file_preprocessor, filepath, preprocessed_filepath)
@tool_executor.exec(command[:line], command[:options])
contents = @preprocessinator_extractor.extract_base_file_from_preprocessed_expansion(preprocessed_filepath)
includes.each{|include| contents.unshift("#include \"#{include}\"")}
@file_wrapper.write(preprocessed_filepath, contents.join("\n"))
end
end

View File

@ -0,0 +1,46 @@
class PreprocessinatorHelper
constructor :configurator, :test_includes_extractor, :task_invoker, :file_finder, :file_path_utils
def preprocess_includes(test, preprocess_includes_proc)
if (@configurator.project_use_test_preprocessor)
preprocessed_includes_list = @file_path_utils.form_preprocessed_includes_list_filepath(test)
preprocess_includes_proc.call( @file_finder.find_test_from_file_path(preprocessed_includes_list) )
@test_includes_extractor.parse_includes_list(preprocessed_includes_list)
else
@test_includes_extractor.parse_test_file(test)
end
end
def assemble_mocks_list(test)
return @file_path_utils.form_mocks_source_filelist( @test_includes_extractor.lookup_raw_mock_list(test) )
end
def preprocess_mockable_headers(mock_list, preprocess_file_proc)
if (@configurator.project_use_test_preprocessor)
preprocess_files_smartly(
@file_path_utils.form_preprocessed_mockable_headers_filelist(mock_list),
preprocess_file_proc ) { |file| @file_finder.find_header_file(file) }
end
end
def preprocess_test_file(test, preprocess_file_proc)
return if (!@configurator.project_use_test_preprocessor)
preprocess_file_proc.call(test)
end
private ############################
def preprocess_files_smartly(file_list, preprocess_file_proc)
if (@configurator.project_use_deep_dependencies)
@task_invoker.invoke_test_preprocessed_files(file_list)
else
file_list.each { |file| preprocess_file_proc.call( yield(file) ) }
end
end
end

View File

@ -0,0 +1,55 @@
class PreprocessinatorIncludesHandler
constructor :configurator, :tool_executor, :task_invoker, :file_path_utils, :yaml_wrapper, :file_wrapper
# shallow includes: only those headers a source file explicitly includes
def invoke_shallow_includes_list(filepath)
@task_invoker.invoke_test_shallow_include_lists( [@file_path_utils.form_preprocessed_includes_list_filepath(filepath)] )
end
# ask the preprocessor for a make-style dependency rule of only the headers the source file immediately includes
def form_shallow_dependencies_rule(filepath)
# change filename (prefix of '_') to prevent preprocessor from finding include files in temp directory containing file it's scanning
temp_filepath = @file_path_utils.form_temp_path(filepath, '_')
# read the file and replace all include statements with a decorated version
# (decorating the names creates file names that don't exist, thus preventing the preprocessor
# from snaking out and discovering the entire include path that winds through the code)
contents = @file_wrapper.read(filepath)
contents.gsub!( /#include\s+\"\s*(\S+)\s*\"/, "#include \"\\1\"\n#include \"@@@@\\1\"" )
@file_wrapper.write( temp_filepath, contents )
# extract the make-style dependency rule telling the preprocessor to
# ignore the fact that it can't find the included files
command = @tool_executor.build_command_line(@configurator.tools_test_includes_preprocessor, temp_filepath)
shell_result = @tool_executor.exec(command[:line], command[:options])
return shell_result[:output]
end
# headers only; ignore any crazy .c includes
def extract_shallow_includes(make_rule)
list = []
header_extension = @configurator.extension_header
headers = make_rule.scan(/(\S+#{'\\'+header_extension})/).flatten # escape slashes before dot file extension
headers.uniq!
headers.map! { |header| header.sub(/(@@@@)|(.+\/)/, '') }
headers.sort!
headers.each_with_index do |header, index|
break if (headers.size == (index-1))
list << header if (header == headers[index + 1])
end
return list
end
def write_shallow_includes_list(filepath, list)
@yaml_wrapper.dump(filepath, list)
end
end

View File

@ -0,0 +1,38 @@
require 'constants'
class ProjectConfigManager
attr_reader :options_files, :release_config_changed, :test_config_changed
attr_accessor :config_hash
constructor :cacheinator, :yaml_wrapper
def setup
@options_files = []
@release_config_changed = false
@test_config_changed = false
end
def merge_options(config_hash, option_filepath)
@options_files << File.basename( option_filepath )
config_hash.deep_merge( @yaml_wrapper.load( option_filepath ) )
return config_hash
end
def process_release_config_change
# has project configuration changed since last release build
@release_config_changed = @cacheinator.diff_cached_release_config?( @config_hash )
end
def process_test_config_change
# has project configuration changed since last test build
@test_config_changed = @cacheinator.diff_cached_test_config?( @config_hash )
end
end

View File

@ -0,0 +1,64 @@
require 'constants'
class ProjectFileLoader
attr_reader :main_file, :user_file
constructor :yaml_wrapper, :stream_wrapper, :system_wrapper, :file_wrapper
def setup
@main_file = nil
@user_file = nil
@main_project_filepath = ''
@user_project_filepath = ''
end
def find_project_files
# first go hunting for optional user project file by looking for environment variable and then default location on disk
user_filepath = @system_wrapper.env_get('CEEDLING_USER_PROJECT_FILE')
if ( not user_filepath.nil? and @file_wrapper.exist?(user_filepath) )
@user_project_filepath = user_filepath
elsif (@file_wrapper.exist?(DEFAULT_CEEDLING_USER_PROJECT_FILE))
@user_project_filepath = DEFAULT_CEEDLING_USER_PROJECT_FILE
end
# next check for main project file by looking for environment variable and then default location on disk;
# blow up if we don't find this guy -- like, he's so totally important
main_filepath = @system_wrapper.env_get('CEEDLING_MAIN_PROJECT_FILE')
if ( not main_filepath.nil? and @file_wrapper.exist?(main_filepath) )
@main_project_filepath = main_filepath
elsif (@file_wrapper.exist?(DEFAULT_CEEDLING_MAIN_PROJECT_FILE))
@main_project_filepath = DEFAULT_CEEDLING_MAIN_PROJECT_FILE
else
# no verbosity checking since this is lowest level reporting anyhow &
# verbosity checking depends on configurator which in turns needs this class (circular dependency)
@stream_wrapper.stderr_puts('Found no Ceedling project file (*.yml)')
raise
end
@main_file = File.basename( @main_project_filepath )
@user_file = File.basename( @user_project_filepath ) if ( not @user_project_filepath.empty? )
end
def load_project_config
config_hash = {}
# if there's no user project file, then just provide hash from project file
if (@user_project_filepath.empty?)
config_hash = @yaml_wrapper.load(@main_project_filepath)
# if there is a user project file, load it too and merge it on top of the project file,
# superseding anything that's common between them
else
config_hash = (@yaml_wrapper.load(@main_project_filepath)).merge(@yaml_wrapper.load(@user_project_filepath))
end
return config_hash
end
end

17
tests/vendor/ceedling/lib/rake_utils.rb vendored Normal file
View File

@ -0,0 +1,17 @@
class RakeUtils
constructor :rake_wrapper
def task_invoked?(task_regex)
task_invoked = false
@rake_wrapper.task_list.each do |task|
if ((task.already_invoked) and (task.to_s =~ task_regex))
task_invoked = true
break
end
end
return task_invoked
end
end

View File

@ -0,0 +1,33 @@
require 'rubygems'
require 'rake'
require 'makefile' # our replacement for rake's make-style dependency loader
include Rake::DSL if defined?(Rake::DSL)
class Rake::Task
attr_reader :already_invoked
end
class RakeWrapper
def initialize
@makefile_loader = Rake::MakefileLoader.new # use our custom replacement noted above
end
def [](task)
return Rake::Task[task]
end
def task_list
return Rake::Task.tasks
end
def create_file_task(file_task, dependencies)
file(file_task => dependencies)
end
def load_dependencies(dependencies_path)
@makefile_loader.load(dependencies_path)
end
end

74
tests/vendor/ceedling/lib/rakefile.rb vendored Normal file
View File

@ -0,0 +1,74 @@
require 'fileutils'
# get directory containing this here file, back up one directory, and expand to full path
CEEDLING_ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
CEEDLING_LIB = File.join(CEEDLING_ROOT, 'lib')
CEEDLING_VENDOR = File.join(CEEDLING_ROOT, 'vendor')
CEEDLING_RELEASE = File.join(CEEDLING_ROOT, 'release')
$LOAD_PATH.unshift( CEEDLING_LIB )
$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'unity/auto') )
$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'diy/lib') )
$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'constructor/lib') )
$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'cmock/lib') )
$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'deep_merge/lib') )
require 'rake'
require 'diy'
require 'constructor'
require 'constants'
require 'target_loader'
# construct all our objects
@ceedling = DIY::Context.from_yaml( File.read( File.join(CEEDLING_LIB, 'objects.yml') ) )
@ceedling.build_everything
# one-stop shopping for all our setup and such after construction
@ceedling[:setupinator].ceedling = @ceedling
project_config =
begin
cfg = @ceedling[:setupinator].load_project_files
TargetLoader.inspect(cfg, ENV['TARGET'])
rescue TargetLoader::NoTargets
cfg
rescue TargetLoader::RequestReload
@ceedling[:setupinator].load_project_files
end
@ceedling[:setupinator].do_setup( project_config )
# tell all our plugins we're about to do something
@ceedling[:plugin_manager].pre_build
# load rakefile component files (*.rake)
PROJECT_RAKEFILE_COMPONENT_FILES.each { |component| load(component) }
# tell rake to shut up by default (overridden in verbosity / debug tasks as appropriate)
verbose(false)
# end block always executed following rake run
END {
# cache our input configurations to use in comparison upon next execution
@ceedling[:cacheinator].cache_test_config( @ceedling[:setupinator].config_hash ) if (@ceedling[:task_invoker].test_invoked?)
@ceedling[:cacheinator].cache_release_config( @ceedling[:setupinator].config_hash ) if (@ceedling[:task_invoker].release_invoked?)
# delete all temp files unless we're in debug mode
if (not @ceedling[:configurator].project_debug)
@ceedling[:file_wrapper].rm_f( @ceedling[:file_wrapper].directory_listing( File.join(@ceedling[:configurator].project_temp_path, '*') ))
end
# only perform these final steps if we got here without runtime exceptions or errors
if (@ceedling[:system_wrapper].ruby_success)
# tell all our plugins the build is done and process results
@ceedling[:plugin_manager].post_build
@ceedling[:plugin_manager].print_plugin_failures
exit(1) if (@ceedling[:plugin_manager].plugins_failed?)
end
}

View File

@ -0,0 +1,58 @@
require 'constants'
class ReleaseInvoker
constructor :configurator, :release_invoker_helper, :build_invoker_utils, :dependinator, :task_invoker, :file_path_utils, :file_wrapper
def setup_and_invoke_c_objects( c_files )
objects = @file_path_utils.form_release_build_c_objects_filelist( c_files )
begin
@release_invoker_helper.process_deep_dependencies( @file_path_utils.form_release_dependencies_filelist( c_files ) )
@dependinator.enhance_release_file_dependencies( objects )
@task_invoker.invoke_release_objects( objects )
rescue => e
@build_invoker_utils.process_exception( e, RELEASE_SYM, false )
end
return objects
end
def setup_and_invoke_asm_objects( asm_files )
objects = @file_path_utils.form_release_build_asm_objects_filelist( asm_files )
begin
@dependinator.enhance_release_file_dependencies( objects )
@task_invoker.invoke_release_objects( objects )
rescue => e
@build_invoker_utils.process_exception( e, RELEASE_SYM, false )
end
return objects
end
def refresh_c_deep_dependencies
return if (not @configurator.project_use_deep_dependencies)
@file_wrapper.rm_f(
@file_wrapper.directory_listing(
File.join( @configurator.project_release_dependencies_path, '*' + @configurator.extension_dependencies ) ) )
@release_invoker_helper.process_deep_dependencies(
@file_path_utils.form_release_dependencies_filelist(
@configurator.collection_all_source ) )
end
def artifactinate( *files )
files.flatten.each do |file|
@file_wrapper.cp( file, @configurator.project_release_artifacts_path ) if @file_wrapper.exist?( file )
end
end
end

View File

@ -0,0 +1,16 @@
class ReleaseInvokerHelper
constructor :configurator, :dependinator, :task_invoker
def process_deep_dependencies(dependencies_list)
return if (not @configurator.project_use_deep_dependencies)
@dependinator.enhance_release_file_dependencies( dependencies_list )
@task_invoker.invoke_release_dependencies_files( dependencies_list )
@dependinator.load_release_object_deep_dependencies( dependencies_list )
end
end

View File

@ -0,0 +1,9 @@
class Reportinator
def generate_banner(message, width=nil)
dash_count = ((width.nil?) ? message.strip.length : width)
return "#{'-' * dash_count}\n#{message}\n#{'-' * dash_count}\n"
end
end

View File

@ -0,0 +1,9 @@
rule(/#{CMOCK_MOCK_PREFIX}.+#{'\\'+EXTENSION_SOURCE}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_header_input_for_mock_file(task_name)
end
]) do |mock|
@ceedling[:generator].generate_mock(TEST_SYM, mock.source)
end

View File

@ -0,0 +1,26 @@
# invocations against this rule should only happen when enhanced dependencies are enabled;
# otherwise, dependency tracking will be too shallow and preprocessed files could intermittently
# fail to be updated when they actually need to be.
rule(/#{PROJECT_TEST_PREPROCESS_FILES_PATH}\/.+/ => [
proc do |task_name|
@ceedling[:file_finder].find_test_or_source_or_header_file(task_name)
end
]) do |file|
if (not @ceedling[:configurator].project_use_deep_dependencies)
raise 'ERROR: Ceedling preprocessing rule invoked though neccessary auxiliary dependency support not enabled.'
end
@ceedling[:generator].generate_preprocessed_file(TEST_SYM, file.source)
end
# invocations against this rule can always happen as there are no deeper dependencies to consider
rule(/#{PROJECT_TEST_PREPROCESS_INCLUDES_PATH}\/.+/ => [
proc do |task_name|
@ceedling[:file_finder].find_test_or_source_or_header_file(task_name)
end
]) do |file|
@ceedling[:generator].generate_shallow_includes_list(TEST_SYM, file.source)
end

View File

@ -0,0 +1,79 @@
RELEASE_COMPILE_TASK_ROOT = RELEASE_TASK_ROOT + 'compile:'
RELEASE_ASSEMBLE_TASK_ROOT = RELEASE_TASK_ROOT + 'assemble:'
if (RELEASE_BUILD_USE_ASSEMBLY)
rule(/#{PROJECT_RELEASE_BUILD_OUTPUT_ASM_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_assembly_file(task_name)
end
]) do |object|
@ceedling[:generator].generate_object_file(
TOOLS_RELEASE_ASSEMBLER,
RELEASE_SYM,
object.source,
object.name )
end
end
rule(/#{PROJECT_RELEASE_BUILD_OUTPUT_C_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |object|
@ceedling[:generator].generate_object_file(
TOOLS_RELEASE_COMPILER,
RELEASE_SYM,
object.source,
object.name,
@ceedling[:file_path_utils].form_release_build_c_list_filepath( object.name ) )
end
rule(/#{PROJECT_RELEASE_BUILD_TARGET}/) do |bin_file|
map_file = @ceedling[:configurator].project_release_build_map
@ceedling[:generator].generate_executable_file(
TOOLS_RELEASE_LINKER,
RELEASE_SYM,
bin_file.prerequisites,
bin_file.name,
map_file )
@ceedling[:release_invoker].artifactinate( bin_file.name, map_file, @ceedling[:configurator].release_build_artifacts )
end
namespace RELEASE_SYM do
# use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks)
namespace :compile do
rule(/^#{RELEASE_COMPILE_TASK_ROOT}\S+#{'\\'+EXTENSION_SOURCE}$/ => [ # compile task names by regex
proc do |task_name|
source = task_name.sub(/#{RELEASE_COMPILE_TASK_ROOT}/, '')
@ceedling[:file_finder].find_source_file(source, :error)
end
]) do |compile|
@ceedling[:rake_wrapper][:directories].invoke
@ceedling[:project_config_manager].process_release_config_change
@ceedling[:release_invoker].setup_and_invoke_c_objects( [compile.source] )
end
end
if (RELEASE_BUILD_USE_ASSEMBLY)
namespace :assemble do
rule(/^#{RELEASE_ASSEMBLE_TASK_ROOT}\S+#{'\\'+EXTENSION_ASSEMBLY}$/ => [ # assemble task names by regex
proc do |task_name|
source = task_name.sub(/#{RELEASE_ASSEMBLE_TASK_ROOT}/, '')
@ceedling[:file_finder].find_assembly_file(source)
end
]) do |assemble|
@ceedling[:rake_wrapper][:directories].invoke
@ceedling[:project_config_manager].process_release_config_change
@ceedling[:release_invoker].setup_and_invoke_asm_objects( [assemble.source] )
end
end
end
end

View File

@ -0,0 +1,15 @@
rule(/#{PROJECT_RELEASE_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |dep|
@ceedling[:generator].generate_dependencies_file(
TOOLS_RELEASE_DEPENDENCIES_GENERATOR,
RELEASE_SYM,
dep.source,
@ceedling[:file_path_utils].form_release_build_c_object_filepath(dep.source),
dep.name)
end

View File

@ -0,0 +1,59 @@
rule(/#{PROJECT_TEST_FILE_PREFIX}#{'.+'+TEST_RUNNER_FILE_SUFFIX}#{'\\'+EXTENSION_SOURCE}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_test_input_for_runner_file(task_name)
end
]) do |runner|
@ceedling[:generator].generate_test_runner(TEST_SYM, runner.source, runner.name)
end
rule(/#{PROJECT_TEST_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |object|
@ceedling[:generator].generate_object_file(
TOOLS_TEST_COMPILER,
TEST_SYM,
object.source,
object.name,
@ceedling[:file_path_utils].form_test_build_list_filepath( object.name ) )
end
rule(/#{PROJECT_TEST_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_EXECUTABLE}$/) do |bin_file|
@ceedling[:generator].generate_executable_file(
TOOLS_TEST_LINKER,
TEST_SYM,
bin_file.prerequisites,
bin_file.name,
@ceedling[:file_path_utils].form_test_build_map_filepath( bin_file.name ) )
end
rule(/#{PROJECT_TEST_RESULTS_PATH}\/#{'.+\\'+EXTENSION_TESTPASS}$/ => [
proc do |task_name|
@ceedling[:file_path_utils].form_test_executable_filepath(task_name)
end
]) do |test_result|
@ceedling[:generator].generate_test_results(TOOLS_TEST_FIXTURE, TEST_SYM, test_result.source, test_result.name)
end
namespace TEST_SYM do
# use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks)
rule(/^#{TEST_TASK_ROOT}\S+$/ => [ # test task names by regex
proc do |task_name|
test = task_name.sub(/#{TEST_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" if not (test.start_with?(PROJECT_TEST_FILE_PREFIX))
@ceedling[:file_finder].find_test_from_file_path(test)
end
]) do |test|
@ceedling[:rake_wrapper][:directories].invoke
@ceedling[:test_invoker].setup_and_invoke([test.source])
end
end

View File

@ -0,0 +1,15 @@
rule(/#{PROJECT_TEST_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |dep|
@ceedling[:generator].generate_dependencies_file(
TOOLS_TEST_DEPENDENCIES_GENERATOR,
TEST_SYM,
dep.source,
@ceedling[:file_path_utils].form_test_build_object_filepath(dep.source),
dep.name)
end

View File

@ -0,0 +1,51 @@
class Setupinator
attr_reader :config_hash
attr_writer :ceedling
def setup
@ceedling = {}
@config_hash = {}
end
def load_project_files
@ceedling[:project_file_loader].find_project_files
return @ceedling[:project_file_loader].load_project_config
end
def do_setup(config_hash)
@config_hash = config_hash
# load up all the constants and accessors our rake files, objects, & external scripts will need;
# note: configurator modifies the cmock section of the hash with a couple defaults to tie
# project together - the modified hash is used to build cmock object
@ceedling[:configurator].populate_defaults( config_hash )
@ceedling[:configurator].populate_cmock_defaults( config_hash )
@ceedling[:configurator].find_and_merge_plugins( config_hash )
@ceedling[:configurator].tools_setup( config_hash )
@ceedling[:configurator].eval_environment_variables( config_hash )
@ceedling[:configurator].eval_paths( config_hash )
@ceedling[:configurator].standardize_paths( config_hash )
@ceedling[:configurator].validate( config_hash )
@ceedling[:configurator].build( config_hash, :environment )
@ceedling[:configurator].insert_rake_plugins( @ceedling[:configurator].rake_plugins )
@ceedling[:configurator].tools_supplement_arguments( config_hash )
# merge in any environment variables plugins specify, after the main build
@ceedling[:plugin_manager].load_plugin_scripts( @ceedling[:configurator].script_plugins, @ceedling ) do |env|
@ceedling[:configurator].eval_environment_variables( env )
@ceedling[:configurator].build_supplement( config_hash, env )
end
@ceedling[:plugin_reportinator].set_system_objects( @ceedling )
@ceedling[:file_finder].prepare_search_sources
@ceedling[:loginator].setup_log_filepath
@ceedling[:project_config_manager].config_hash = config_hash
end
def reset_defaults(config_hash)
@ceedling[:configurator].reset_defaults( config_hash )
end
end

View File

@ -0,0 +1,20 @@
class StreamWrapper
def stdout_puts(string)
$stdout.puts(string)
end
def stdout_flush
$stdout.flush
end
def stderr_puts(string)
$stderr.puts(string)
end
def stderr_flush
$stderr.flush
end
end

View File

@ -0,0 +1,41 @@
class Streaminator
require 'constants'
constructor :streaminator_helper, :verbosinator, :loginator, :stream_wrapper
# for those objects for whom the configurator has already been instantiated,
# Streaminator is a convenience object for handling verbosity and writing to the std streams
def stdout_puts(string, verbosity=Verbosity::NORMAL)
if (@verbosinator.should_output?(verbosity))
@stream_wrapper.stdout_puts(string)
@stream_wrapper.stdout_flush
end
# write to log as though Verbosity::OBNOXIOUS
@loginator.log( string, @streaminator_helper.extract_name($stdout) )
end
def stderr_puts(string, verbosity=Verbosity::NORMAL)
if (@verbosinator.should_output?(verbosity))
@stream_wrapper.stderr_puts(string)
@stream_wrapper.stderr_flush
end
# write to log as though Verbosity::OBNOXIOUS
@loginator.log( string, @streaminator_helper.extract_name($stderr) )
end
def stream_puts(stream, string, verbosity=Verbosity::NORMAL)
if (@verbosinator.should_output?(verbosity))
stream.puts(string)
stream.flush
end
# write to log as though Verbosity::OBNOXIOUS
@loginator.log( string, @streaminator_helper.extract_name(stream) )
end
end

View File

@ -0,0 +1,15 @@
class StreaminatorHelper
def extract_name(stream)
name = case (stream.fileno)
when 0 then '#<IO:$stdin>'
when 1 then '#<IO:$stdout>'
when 2 then '#<IO:$stderr>'
else stream.inspect
end
return name
end
end

View File

@ -0,0 +1,32 @@
class Object
def deep_clone
Marshal::load(Marshal.dump(self))
end
end
class SystemUtils
constructor :system_wrapper
def setup
@tcsh_shell = nil
end
def tcsh_shell?
# once run a single time, return state determined at that execution
return @tcsh_shell if not @tcsh_shell.nil?
result = @system_wrapper.shell_backticks('echo $version')
if ((result[:exit_code] == 0) and (result[:output].strip =~ /^tcsh/))
@tcsh_shell = true
else
@tcsh_shell = false
end
return @tcsh_shell
end
end

View File

@ -0,0 +1,76 @@
require 'rbconfig'
class SystemWrapper
# static method for use in defaults
def self.windows?
return ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false) if defined?(RbConfig)
return ((Config::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false)
end
# class method so as to be mockable for tests
def windows?
return SystemWrapper.windows?
end
def module_eval(string)
return Object.module_eval("\"" + string + "\"")
end
def eval(string)
return eval(string)
end
def search_paths
return ENV['PATH'].split(File::PATH_SEPARATOR)
end
def cmdline_args
return ARGV
end
def env_set(name, value)
ENV[name] = value
end
def env_get(name)
return ENV[name]
end
def time_now
return Time.now.asctime
end
def shell_backticks(command)
return {
:output => `#{command}`.freeze,
:exit_code => ($?.exitstatus).freeze
}
end
def shell_system(command)
system( command )
return {
:output => ''.freeze,
:exit_code => ($?.exitstatus).freeze
}
end
def add_load_path(path)
$LOAD_PATH.unshift(path)
end
def require_file(path)
require(path)
end
def ruby_success
return ($!.nil? || $!.is_a?(SystemExit) && $!.success?)
end
def constants_include?(item)
# forcing to strings provides consistency across Ruby versions
return Object.constants.map{|constant| constant.to_s}.include?(item.to_s)
end
end

View File

@ -0,0 +1,38 @@
module TargetLoader
class NoTargets < Exception; end
class NoDirectory < Exception; end
class NoDefault < Exception; end
class NoSuchTarget < Exception; end
class RequestReload < Exception; end
def self.inspect(config, target_name=nil)
unless config[:targets]
raise NoTargets
end
targets = config[:targets]
unless targets[:targets_directory]
raise NoDirectory("No targets directory specified.")
end
unless targets[:default_target]
raise NoDefault("No default target specified.")
end
target_path = lambda {|name| File.join(targets[:targets_directory], name + ".yml")}
target = if target_name
target_path.call(target_name)
else
target_path.call(targets[:default_target])
end
unless File.exists? target
raise NoSuchTarget.new("No such target: #{target}")
end
ENV['CEEDLING_MAIN_PROJECT_FILE'] = target
raise RequestReload
end
end

View File

@ -0,0 +1,89 @@
require "par_map"
class TaskInvoker
constructor :dependinator, :rake_utils, :rake_wrapper
def setup
@test_regexs = [/^#{TEST_ROOT_NAME}:/]
@release_regexs = [/^#{RELEASE_ROOT_NAME}(:|$)/]
end
def add_test_task_regex(regex)
@test_regexs << regex
end
def add_release_task_regex(regex)
@release_regexs << regex
end
def test_invoked?
invoked = false
@test_regexs.each do |regex|
invoked = true if (@rake_utils.task_invoked?(regex))
break if invoked
end
return invoked
end
def release_invoked?
invoked = false
@release_regexs.each do |regex|
invoked = true if (@rake_utils.task_invoked?(regex))
break if invoked
end
return invoked
end
def invoked?(regex)
return @rake_utils.task_invoked?(regex)
end
def invoke_test_mocks(mocks)
@dependinator.enhance_mock_dependencies( mocks )
mocks.each { |mock| @rake_wrapper[mock].invoke }
end
def invoke_test_runner(runner)
@dependinator.enhance_runner_dependencies( runner )
@rake_wrapper[runner].invoke
end
def invoke_test_shallow_include_lists(files)
@dependinator.enhance_shallow_include_lists_dependencies( files )
files.each { |file| @rake_wrapper[file].invoke }
end
def invoke_test_preprocessed_files(files)
@dependinator.enhance_preprocesed_file_dependencies( files )
files.each { |file| @rake_wrapper[file].invoke }
end
def invoke_test_dependencies_files(files)
@dependinator.enhance_dependencies_dependencies( files )
files.each { |file| @rake_wrapper[file].invoke }
end
def invoke_test_results(result)
@dependinator.enhance_results_dependencies( result )
@rake_wrapper[result].invoke
end
def invoke_release_dependencies_files(files)
par_map(PROJECT_COMPILE_THREADS, files) do |file|
@rake_wrapper[file].invoke
end
end
def invoke_release_objects(objects)
par_map(PROJECT_COMPILE_THREADS, objects) do |object|
@rake_wrapper[object].invoke
end
end
end

View File

@ -0,0 +1,104 @@
require 'constants'
require 'file_path_utils'
desc "Display build environment version info."
task :version do
tools = [
[' Ceedling', CEEDLING_ROOT],
['CException', File.join( CEEDLING_VENDOR, CEXCEPTION_ROOT_PATH)],
[' CMock', File.join( CEEDLING_VENDOR, CMOCK_ROOT_PATH)],
[' Unity', File.join( CEEDLING_VENDOR, UNITY_ROOT_PATH)],
]
tools.each do |tool|
name = tool[0]
base_path = tool[1]
version_string = @ceedling[:file_wrapper].read( File.join(base_path, 'release', 'version.info') ).strip
build_string = @ceedling[:file_wrapper].read( File.join(base_path, 'release', 'build.info') ).strip
puts "#{name}:: #{version_string.empty? ? '#.#.' : (version_string + '.')}#{build_string.empty? ? '?' : build_string}"
end
end
desc "Set verbose output (silent:[#{Verbosity::SILENT}] - obnoxious:[#{Verbosity::OBNOXIOUS}])."
task :verbosity, :level do |t, args|
verbosity_level = args.level.to_i
if (PROJECT_USE_MOCKS)
# don't store verbosity level in setupinator's config hash, use a copy;
# otherwise, the input configuration will change and trigger entire project rebuilds
hash = @ceedling[:setupinator].config_hash[:cmock].clone
hash[:verbosity] = verbosity_level
@ceedling[:cmock_builder].manufacture( hash )
end
@ceedling[:configurator].project_verbosity = verbosity_level
# control rake's verbosity with new setting
verbose( ((verbosity_level >= Verbosity::OBNOXIOUS) ? true : false) )
end
desc "Enable logging"
task :logging do
@ceedling[:configurator].project_logging = true
end
# non advertised debug task
task :debug do
Rake::Task[:verbosity].invoke(Verbosity::DEBUG)
Rake.application.options.trace = true
@ceedling[:configurator].project_debug = true
end
# non advertised sanity checking task
task :sanity_checks, :level do |t, args|
check_level = args.level.to_i
@ceedling[:configurator].sanity_checks = check_level
end
# list expanded environment variables
if (not ENVIRONMENT.empty?)
desc "List all configured environment variables."
task :environment do
ENVIRONMENT.each do |env|
env.each_key do |key|
name = key.to_s.upcase
puts " - #{name}: \"#{env[key]}\""
end
end
end
end
namespace :options do
COLLECTION_PROJECT_OPTIONS.each do |option_path|
option = File.basename(option_path, '.yml')
desc "Merge #{option} project options."
task option.downcase.to_sym do
# @ceedling[:setupinator].reset_defaults( @ceedling[:setupinator].config_hash )
hash = @ceedling[:project_config_manager].merge_options( @ceedling[:setupinator].config_hash, option_path )
@ceedling[:setupinator].do_setup( hash )
end
end
end
# do not present task if there's no plugins
if (not PLUGINS_ENABLED.empty?)
desc "Execute plugin result summaries (no build triggering)."
task :summary do
@ceedling[:plugin_manager].summary
puts "\nNOTE: Summaries may be out of date with project sources.\n\n"
end
end

View File

@ -0,0 +1,91 @@
# rather than require 'rake/clean' & try to override, we replicate for finer control
CLEAN = Rake::FileList["**/*~", "**/*.bak"]
CLOBBER = Rake::FileList.new
CLEAN.clear_exclude.exclude { |fn| fn.pathmap("%f") == 'core' && File.directory?(fn) }
CLEAN.include(File.join(PROJECT_TEST_BUILD_OUTPUT_PATH, '*'))
CLEAN.include(File.join(PROJECT_TEST_RESULTS_PATH, '*'))
CLEAN.include(File.join(PROJECT_TEST_DEPENDENCIES_PATH, '*'))
CLEAN.include(File.join(PROJECT_BUILD_RELEASE_ROOT, '*.*'))
CLEAN.include(File.join(PROJECT_RELEASE_BUILD_OUTPUT_PATH, '*'))
CLEAN.include(File.join(PROJECT_RELEASE_DEPENDENCIES_PATH, '*'))
CLOBBER.include(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, '**/*'))
CLOBBER.include(File.join(PROJECT_BUILD_TESTS_ROOT, '**/*'))
CLOBBER.include(File.join(PROJECT_BUILD_RELEASE_ROOT, '**/*'))
CLOBBER.include(File.join(PROJECT_LOG_PATH, '**/*'))
CLOBBER.include(File.join(PROJECT_TEMP_PATH, '**/*'))
# because of cmock config, mock path can optionally exist apart from standard test build paths
CLOBBER.include(File.join(CMOCK_MOCK_PATH, '*'))
REMOVE_FILE_PROC = Proc.new { |fn| rm_r fn rescue nil }
# redefine clean so we can override how it advertises itself
desc "Delete all build artifacts and temporary products."
task(:clean) do
# because :clean is a prerequisite for :clobber, intelligently display the progress message
if (not @ceedling[:task_invoker].invoked?(/^clobber$/))
@ceedling[:streaminator].stdout_puts("\nCleaning build artifacts...\n(For large projects, this task may take a long time to complete)\n\n")
end
CLEAN.each { |fn| REMOVE_FILE_PROC.call(fn) }
end
# redefine clobber so we can override how it advertises itself
desc "Delete all generated files (and build artifacts)."
task(:clobber => [:clean]) do
@ceedling[:streaminator].stdout_puts("\nClobbering all generated files...\n(For large projects, this task may take a long time to complete)\n\n")
CLOBBER.each { |fn| REMOVE_FILE_PROC.call(fn) }
end
PROJECT_BUILD_PATHS.each { |path| directory(path) }
# create directories that hold build output and generated files & touching rebuild dependency sources
task(:directories => PROJECT_BUILD_PATHS) { @ceedling[:dependinator].touch_force_rebuild_files }
# list paths discovered at load time
namespace :paths do
paths = @ceedling[:setupinator].config_hash[:paths]
paths.each_key do |section|
name = section.to_s.downcase
path_list = Object.const_get("COLLECTION_PATHS_#{name.upcase}")
if (path_list.size != 0)
desc "List all collected #{name} paths."
task(name.to_sym) { puts "#{name} paths:"; path_list.sort.each {|path| puts " - #{path}" } }
end
end
end
# list files & file counts discovered at load time
namespace :files do
categories = [
['test', COLLECTION_ALL_TESTS],
['source', COLLECTION_ALL_SOURCE],
['header', COLLECTION_ALL_HEADERS]
]
categories << ['assembly', COLLECTION_ALL_ASSEMBLY] if (RELEASE_BUILD_USE_ASSEMBLY)
categories.each do |category|
name = category[0]
collection = category[1]
desc "List all collected #{name} files."
task(name.to_sym) do
puts "#{name} files:"
collection.sort.each { |filepath| puts " - #{filepath}" }
puts "file count: #{collection.size}"
end
end
end

View File

@ -0,0 +1,28 @@
require 'constants'
require 'file_path_utils'
desc "Build release target."
task RELEASE_SYM => [:directories] do
header = "Release build '#{File.basename(PROJECT_RELEASE_BUILD_TARGET)}'"
@ceedling[:streaminator].stdout_puts("\n\n#{header}\n#{'-' * header.length}")
begin
@ceedling[:plugin_manager].pre_release
core_objects = []
extra_objects = @ceedling[:file_path_utils].form_release_build_c_objects_filelist( COLLECTION_RELEASE_ARTIFACT_EXTRA_LINK_OBJECTS )
@ceedling[:project_config_manager].process_release_config_change
core_objects.concat( @ceedling[:release_invoker].setup_and_invoke_c_objects( COLLECTION_ALL_SOURCE ) )
# if assembler use isn't enabled, COLLECTION_ALL_ASSEMBLY is empty array & nothing happens
core_objects.concat( @ceedling[:release_invoker].setup_and_invoke_asm_objects( COLLECTION_ALL_ASSEMBLY ) )
file( PROJECT_RELEASE_BUILD_TARGET => (core_objects + extra_objects) )
Rake::Task[PROJECT_RELEASE_BUILD_TARGET].invoke
ensure
@ceedling[:plugin_manager].post_release
end
end

View File

@ -0,0 +1,9 @@
require 'constants'
namespace REFRESH_SYM do
task RELEASE_SYM do
@ceedling[:release_invoker].refresh_c_deep_dependencies
end
end

View File

@ -0,0 +1,52 @@
require 'constants'
namespace TEST_SYM do
desc "Run all unit tests."
task :all => [:directories] do
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS)
end
desc "Run single test ([*] real test or source file name, no path)."
task :* do
message = "\nOops! '#{TEST_ROOT_NAME}:*' isn't a real task. " +
"Use a real test or source file name (no path) in place of the wildcard.\n" +
"Example: rake #{TEST_ROOT_NAME}:foo.c\n\n"
@ceedling[:streaminator].stdout_puts( message )
end
desc "Run tests for changed files."
task :delta => [:directories] do
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, TEST_SYM, {:force_run => false})
end
desc "Run tests by matching regular expression pattern."
task :pattern, [:regex] => [:directories] do |t, args|
matches = []
COLLECTION_ALL_TESTS.each { |test| matches << test if (test =~ /#{args.regex}/) }
if (matches.size > 0)
@ceedling[:test_invoker].setup_and_invoke(matches, TEST_SYM, {:force_run => false})
else
@ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.")
end
end
desc "Run tests whose test path contains [dir] or [dir] substring."
task :path, [:dir] => [:directories] do |t, args|
matches = []
COLLECTION_ALL_TESTS.each { |test| matches << test if File.dirname(test).include?(args.dir.gsub(/\\/, '/')) }
if (matches.size > 0)
@ceedling[:test_invoker].setup_and_invoke(matches, TEST_SYM, {:force_run => false})
else
@ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.")
end
end
end

View File

@ -0,0 +1,9 @@
require 'constants'
namespace REFRESH_SYM do
task TEST_SYM do
@ceedling[:test_invoker].refresh_deep_dependencies
end
end

View File

@ -0,0 +1,36 @@
require 'constants'
require 'file_path_utils'
# create file dependencies to ensure C-based components of vendor tools are recompiled when they are updated with new versions
# forming these explicitly rather than depend on auxiliary dependencies so all scenarios are explicitly covered
file( @ceedling[:file_path_utils].form_test_build_object_filepath( UNITY_C_FILE ) => [
FilePathUtils.form_ceedling_vendor_path( UNITY_LIB_PATH, UNITY_C_FILE ),
FilePathUtils.form_ceedling_vendor_path( UNITY_LIB_PATH, UNITY_H_FILE ),
FilePathUtils.form_ceedling_vendor_path( UNITY_LIB_PATH, UNITY_INTERNALS_H_FILE ) ]
)
if (PROJECT_USE_MOCKS)
file( @ceedling[:file_path_utils].form_test_build_object_filepath( CMOCK_C_FILE ) => [
FilePathUtils.form_ceedling_vendor_path( CMOCK_LIB_PATH, CMOCK_C_FILE ),
FilePathUtils.form_ceedling_vendor_path( CMOCK_LIB_PATH, CMOCK_H_FILE ) ]
)
end
if (PROJECT_USE_EXCEPTIONS)
file( @ceedling[:file_path_utils].form_test_build_object_filepath( CEXCEPTION_C_FILE ) => [
FilePathUtils.form_ceedling_vendor_path( CEXCEPTION_LIB_PATH, CEXCEPTION_C_FILE ),
FilePathUtils.form_ceedling_vendor_path( CEXCEPTION_LIB_PATH, CEXCEPTION_H_FILE ) ]
)
end
if (PROJECT_USE_EXCEPTIONS and PROJECT_RELEASE_BUILD)
file( @ceedling[:file_path_utils].form_release_build_c_object_filepath( CEXCEPTION_C_FILE ) => [
FilePathUtils.form_ceedling_vendor_path( CEXCEPTION_LIB_PATH, CEXCEPTION_C_FILE ),
FilePathUtils.form_ceedling_vendor_path( CEXCEPTION_LIB_PATH, CEXCEPTION_H_FILE ) ]
)
end

View File

@ -0,0 +1,81 @@
class TestIncludesExtractor
constructor :configurator, :yaml_wrapper, :file_wrapper
def setup
@includes = {}
@mocks = {}
end
# for includes_list file, slurp up array from yaml file and sort & store includes
def parse_includes_list(includes_list)
gather_and_store_includes( includes_list, @yaml_wrapper.load(includes_list) )
end
# open, scan for, and sort & store includes of test file
def parse_test_file(test)
gather_and_store_includes( test, extract_from_file(test) )
end
# mocks with no file extension
def lookup_raw_mock_list(test)
file_key = form_file_key(test)
return [] if @mocks[file_key].nil?
return @mocks[file_key]
end
# includes with file extension
def lookup_includes_list(file)
file_key = form_file_key(file)
return [] if (@includes[file_key]).nil?
return @includes[file_key]
end
private #################################
def form_file_key(filepath)
return File.basename(filepath).to_sym
end
def extract_from_file(file)
includes = []
header_extension = @configurator.extension_header
contents = @file_wrapper.read(file)
# remove line comments
contents = contents.gsub(/\/\/.*$/, '')
# remove block comments
contents = contents.gsub(/\/\*.*?\*\//m, '')
contents.split("\n").each do |line|
# look for include statement
scan_results = line.scan(/#include\s+\"\s*(.+#{'\\'+header_extension})\s*\"/)
includes << scan_results[0][0] if (scan_results.size > 0)
end
return includes.uniq
end
def gather_and_store_includes(file, includes)
mock_prefix = @configurator.cmock_mock_prefix
header_extension = @configurator.extension_header
file_key = form_file_key(file)
@mocks[file_key] = []
# add includes to lookup hash
@includes[file_key] = includes
includes.each do |include_file|
# check if include is a mock
scan_results = include_file.scan(/(#{mock_prefix}.+)#{'\\'+header_extension}/)
# add mock to lookup hash
@mocks[file_key] << scan_results[0][0] if (scan_results.size > 0)
end
end
end

View File

@ -0,0 +1,97 @@
require 'constants'
class TestInvoker
attr_reader :sources, :tests, :mocks
constructor :configurator,
:test_invoker_helper,
:plugin_manager,
:streaminator,
:preprocessinator,
:task_invoker,
:dependinator,
:project_config_manager,
:build_invoker_utils,
:file_path_utils,
:file_wrapper
def setup
@sources = []
@tests = []
@mocks = []
end
def setup_and_invoke(tests, context=TEST_SYM, options={:force_run => true})
@tests = tests
@project_config_manager.process_test_config_change
@tests.each do |test|
# announce beginning of test run
header = "Test '#{File.basename(test)}'"
@streaminator.stdout_puts("\n\n#{header}\n#{'-' * header.length}")
begin
@plugin_manager.pre_test
# collect up test fixture pieces & parts
runner = @file_path_utils.form_runner_filepath_from_test( test )
mock_list = @preprocessinator.preprocess_test_and_invoke_test_mocks( test )
sources = @test_invoker_helper.extract_sources( test )
extras = @configurator.collection_test_fixture_extra_link_objects
core = [test] + mock_list + sources
objects = @file_path_utils.form_test_build_objects_filelist( [runner] + core + extras )
results_pass = @file_path_utils.form_pass_results_filepath( test )
results_fail = @file_path_utils.form_fail_results_filepath( test )
# clean results files so we have a missing file with which to kick off rake's dependency rules
@test_invoker_helper.clean_results( {:pass => results_pass, :fail => results_fail}, options )
# load up auxiliary dependencies so deep changes cause rebuilding appropriately
@test_invoker_helper.process_deep_dependencies( core ) do |dependencies_list|
@dependinator.load_test_object_deep_dependencies( dependencies_list )
end
# tell rake to create test runner if needed
@task_invoker.invoke_test_runner( runner )
# enhance object file dependencies to capture externalities influencing regeneration
@dependinator.enhance_test_build_object_dependencies( objects )
# associate object files with executable
@dependinator.setup_test_executable_dependencies( test, objects )
# 3, 2, 1... launch
@task_invoker.invoke_test_results( results_pass )
rescue => e
@build_invoker_utils.process_exception( e, context )
ensure
@plugin_manager.post_test
end
# store away what's been processed
@mocks.concat( mock_list )
@sources.concat( sources )
end
# post-process collected mock list
@mocks.uniq!
# post-process collected sources list
@sources.uniq!
end
def refresh_deep_dependencies
@file_wrapper.rm_f(
@file_wrapper.directory_listing(
File.join( @configurator.project_test_dependencies_path, '*' + @configurator.extension_dependencies ) ) )
@test_invoker_helper.process_deep_dependencies(
@configurator.collection_all_tests + @configurator.collection_all_source )
end
end

View File

@ -0,0 +1,28 @@
class TestInvokerHelper
constructor :configurator, :task_invoker, :test_includes_extractor, :file_finder, :file_path_utils, :file_wrapper
def clean_results(results, options)
@file_wrapper.rm_f( results[:fail] )
@file_wrapper.rm_f( results[:pass] ) if (options[:force_run])
end
def process_deep_dependencies(files)
return if (not @configurator.project_use_deep_dependencies)
dependencies_list = @file_path_utils.form_test_dependencies_filelist( files )
@task_invoker.invoke_test_dependencies_files( dependencies_list )
yield( dependencies_list ) if block_given?
end
def extract_sources(test)
sources = []
includes = @test_includes_extractor.lookup_includes_list(test)
includes.each { |include| sources << @file_finder.find_compilation_input_file(include, :ignore) }
return sources.compact
end
end

View File

@ -0,0 +1,212 @@
require 'constants'
class ShellExecutionException < RuntimeError
attr_reader :shell_result
def initialize(shell_result)
@shell_result = shell_result
end
end
class ToolExecutor
constructor :configurator, :tool_executor_helper, :streaminator, :system_wrapper
def setup
@tool_name = ''
@executable = ''
end
# build up a command line from yaml provided config
def build_command_line(tool_config, *args)
@tool_name = tool_config[:name]
@executable = tool_config[:executable]
command = {}
# basic premise is to iterate top to bottom through arguments using '$' as
# a string replacement indicator to expand globals or inline yaml arrays
# into command line arguments via substitution strings
command[:line] = [
@tool_executor_helper.osify_path_separators( expandify_element(@executable, *args) ),
build_arguments(tool_config[:arguments], *args),
].join(' ').strip
command[:options] = {
:stderr_redirect => @tool_executor_helper.stderr_redirection(tool_config, @configurator.project_logging),
:background_exec => tool_config[:background_exec]
}
return command
end
# shell out, execute command, and return response
def exec(command, options={}, args=[])
options[:boom] = true if (options[:boom].nil?)
options[:stderr_redirect] = StdErrRedirect::NONE if (options[:stderr_redirect].nil?)
options[:background_exec] = BackgroundExec::NONE if (options[:background_exec].nil?)
# build command line
command_line = [
@tool_executor_helper.background_exec_cmdline_prepend( options ),
command.strip,
args,
@tool_executor_helper.stderr_redirect_cmdline_append( options ),
@tool_executor_helper.background_exec_cmdline_append( options ),
].flatten.compact.join(' ')
shell_result = {}
# depending on background exec option, we shell out differently
if (options[:background_exec] != BackgroundExec::NONE)
shell_result = @system_wrapper.shell_system( command_line )
else
shell_result = @system_wrapper.shell_backticks( command_line )
end
@tool_executor_helper.print_happy_results( command_line, shell_result, options[:boom] )
@tool_executor_helper.print_error_results( command_line, shell_result, options[:boom] )
# go boom if exit code isn't 0 (but in some cases we don't want a non-0 exit code to raise)
raise ShellExecutionException.new(shell_result) if ((shell_result[:exit_code] != 0) and options[:boom])
return shell_result
end
private #############################
def build_arguments(config, *args)
build_string = ''
return nil if (config.nil?)
# iterate through each argument
# the yaml blob array needs to be flattened so that yaml substitution
# is handled correctly, since it creates a nested array when an anchor is
# dereferenced
config.flatten.each do |element|
argument = ''
case(element)
# if we find a simple string then look for string replacement operators
# and expand with the parameters in this method's argument list
when String then argument = expandify_element(element, *args)
# if we find a hash, then we grab the key as a substitution string and expand the
# hash's value(s) within that substitution string
when Hash then argument = dehashify_argument_elements(element)
end
build_string.concat("#{argument} ") if (argument.length > 0)
end
build_string.strip!
return build_string if (build_string.length > 0)
return nil
end
# handle simple text string argument & argument array string replacement operators
def expandify_element(element, *args)
match = //
to_process = nil
args_index = 0
# handle ${#} input replacement
if (element =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN)
args_index = ($2.to_i - 1)
if (args.nil? or args[args_index].nil?)
@streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' expected valid argument data to accompany replacement operator #{$1}.", Verbosity::ERRORS)
raise
end
match = /#{Regexp.escape($1)}/
to_process = args[args_index]
end
# simple string argument: replace escaped '\$' and strip
element.sub!(/\\\$/, '$')
element.strip!
# handle inline ruby execution
if (element =~ RUBY_EVAL_REPLACEMENT_PATTERN)
element.replace(eval($1))
end
build_string = ''
# handle array or anything else passed into method to be expanded in place of replacement operators
case (to_process)
when Array then to_process.each {|value| build_string.concat( "#{element.sub(match, value.to_s)} " ) } if (to_process.size > 0)
else build_string.concat( element.sub(match, to_process.to_s) )
end
# handle inline ruby string substitution
if (build_string =~ RUBY_STRING_REPLACEMENT_PATTERN)
build_string.replace(@system_wrapper.module_eval(build_string))
end
return build_string.strip
end
# handle argument hash: keys are substitution strings, values are data to be expanded within substitution strings
def dehashify_argument_elements(hash)
build_string = ''
elements = []
# grab the substitution string (hash key)
substitution = hash.keys[0].to_s
# grab the string(s) to squirt into the substitution string (hash value)
expand = hash[hash.keys[0]]
if (expand.nil?)
@streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' could not expand nil elements for substitution string '#{substitution}'.", Verbosity::ERRORS)
raise
end
# array-ify expansion input if only a single string
expansion = ((expand.class == String) ? [expand] : expand)
expansion.each do |item|
# code eval substitution
if (item =~ RUBY_EVAL_REPLACEMENT_PATTERN)
elements << eval($1)
# string eval substitution
elsif (item =~ RUBY_STRING_REPLACEMENT_PATTERN)
elements << @system_wrapper.module_eval(item)
# global constants
elsif (@system_wrapper.constants_include?(item))
const = Object.const_get(item)
if (const.nil?)
@streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' found constant '#{item}' to be nil.", Verbosity::ERRORS)
raise
else
elements << const
end
elsif (item.class == Array)
elements << item
elsif (item.class == String)
@streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' cannot expand nonexistent value '#{item}' for substitution string '#{substitution}'.", Verbosity::ERRORS)
raise
else
@streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' cannot expand value having type '#{item.class}' for substitution string '#{substitution}'.", Verbosity::ERRORS)
raise
end
end
# expand elements (whether string or array) into substitution string & replace escaped '\$'
elements.flatten!
elements.each do |element|
build_string.concat( substitution.sub(/([^\\]*)\$/, "\\1#{element}") ) # don't replace escaped '\$' but allow us to replace just a lonesome '$'
build_string.gsub!(/\\\$/, '$')
build_string.concat(' ')
end
return build_string.strip
end
end

View File

@ -0,0 +1,115 @@
require 'constants' # for Verbosity enumeration & $stderr redirect enumeration
class ToolExecutorHelper
constructor :streaminator, :system_utils, :system_wrapper
def stderr_redirection(tool_config, logging)
# if there's no logging enabled, return :stderr_redirect unmodified
return tool_config[:stderr_redirect] if (not logging)
# if there is logging enabled but the redirect is a custom value (not enum), return the custom string
return tool_config[:stderr_redirect] if (tool_config[:stderr_redirect].class == String)
# if logging is enabled but there's no custom string, return the AUTO enumeration so $stderr goes into the log
return StdErrRedirect::AUTO
end
def background_exec_cmdline_prepend(tool_config)
return nil if (tool_config[:background_exec].nil?)
config_exec = tool_config[:background_exec]
if ((config_exec == BackgroundExec::AUTO) and (@system_wrapper.windows?))
return 'start'
end
if (config_exec == BackgroundExec::WIN)
return 'start'
end
return nil
end
def osify_path_separators(executable)
return executable.gsub(/\//, '\\') if (@system_wrapper.windows?)
return executable
end
def stderr_redirect_cmdline_append(tool_config)
return nil if (tool_config[:stderr_redirect].nil?)
config_redirect = tool_config[:stderr_redirect]
redirect = StdErrRedirect::NONE
if (config_redirect == StdErrRedirect::AUTO)
if (@system_wrapper.windows?)
redirect = StdErrRedirect::WIN
else
if (@system_utils.tcsh_shell?)
redirect = StdErrRedirect::TCSH
else
redirect = StdErrRedirect::UNIX
end
end
end
case redirect
# we may need more complicated processing after some learning with various environments
when StdErrRedirect::NONE then nil
when StdErrRedirect::WIN then '2>&1'
when StdErrRedirect::UNIX then '2>&1'
when StdErrRedirect::TCSH then '|&'
else redirect.to_s
end
end
def background_exec_cmdline_append(tool_config)
return nil if (tool_config[:background_exec].nil?)
config_exec = tool_config[:background_exec]
# if :auto & windows, then we already prepended 'start' and should append nothing
return nil if ((config_exec == BackgroundExec::AUTO) and (@system_wrapper.windows?))
# if :auto & not windows, then we append standard '&'
return '&' if ((config_exec == BackgroundExec::AUTO) and (not @system_wrapper.windows?))
# if explicitly Unix, then append '&'
return '&' if (config_exec == BackgroundExec::UNIX)
# all other cases, including :none, :win, & anything unrecognized, append nothing
return nil
end
# if command succeeded and we have verbosity cranked up, spill our guts
def print_happy_results(command_str, shell_result, boom=true)
if ((shell_result[:exit_code] == 0) or ((shell_result[:exit_code] != 0) and not boom))
output = "> Shell executed command:\n"
output += "#{command_str}\n"
output += "> Produced output:\n" if (not shell_result[:output].empty?)
output += "#{shell_result[:output].strip}\n" if (not shell_result[:output].empty?)
output += "> And exited with status: [#{shell_result[:exit_code]}].\n" if (shell_result[:exit_code] != 0)
output += "\n"
@streaminator.stdout_puts(output, Verbosity::OBNOXIOUS)
end
end
# if command failed and we have verbosity set to minimum error level, spill our guts
def print_error_results(command_str, shell_result, boom=true)
if ((shell_result[:exit_code] != 0) and boom)
output = "ERROR: Shell command failed.\n"
output += "> Shell executed command:\n"
output += "'#{command_str}'\n"
output += "> Produced output:\n" if (not shell_result[:output].empty?)
output += "#{shell_result[:output].strip}\n" if (not shell_result[:output].empty?)
output += "> And exited with status: [#{shell_result[:exit_code]}].\n" if (shell_result[:exit_code] != nil)
output += "> And then likely crashed.\n" if (shell_result[:exit_code] == nil)
output += "\n"
@streaminator.stderr_puts(output, Verbosity::ERRORS)
end
end
end

View File

@ -0,0 +1,10 @@
class Verbosinator
constructor :configurator
def should_output?(level)
return (level <= @configurator.project_verbosity)
end
end

View File

@ -0,0 +1,16 @@
require 'yaml'
class YamlWrapper
def load(filepath)
return YAML.load(File.read(filepath))
end
def dump(filepath, structure)
File.open(filepath, 'w') do |output|
YAML.dump(structure, output)
end
end
end

View File

@ -0,0 +1,15 @@
% function_string = hash[:coverage][:functions].to_s
% branch_string = hash[:coverage][:branches].to_s
% format_string = "%#{[function_string.length, branch_string.length].max}i"
<%=@ceedling[:plugin_reportinator].generate_banner("#{hash[:header]}: CODE COVERAGE SUMMARY")%>
% if (!hash[:coverage][:functions].nil?)
FUNCTIONS: <%=sprintf(format_string, hash[:coverage][:functions])%>%
% else
FUNCTIONS: none
% end
% if (!hash[:coverage][:branches].nil?)
BRANCHES: <%=sprintf(format_string, hash[:coverage][:branches])%>%
% else
BRANCHES: none
% end

View File

@ -0,0 +1,162 @@
directory(BULLSEYE_BUILD_OUTPUT_PATH)
directory(BULLSEYE_RESULTS_PATH)
directory(BULLSEYE_ARTIFACTS_PATH)
directory(BULLSEYE_DEPENDENCIES_PATH)
CLEAN.include(File.join(BULLSEYE_BUILD_OUTPUT_PATH, '*'))
CLEAN.include(File.join(BULLSEYE_RESULTS_PATH, '*'))
CLEAN.include(File.join(BULLSEYE_DEPENDENCIES_PATH, '*'))
CLOBBER.include(File.join(BULLSEYE_BUILD_PATH, '**/*'))
rule(/#{BULLSEYE_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |object|
if (File.basename(object.source) =~ /^(#{PROJECT_TEST_FILE_PREFIX}|#{CMOCK_MOCK_PREFIX}|#{BULLSEYE_IGNORE_SOURCES.join('|')})/i)
@ceedling[:generator].generate_object_file(
TOOLS_BULLSEYE_COMPILER,
BULLSEYE_SYM,
object.source,
object.name,
@ceedling[:file_path_utils].form_test_build_list_filepath( object.name ) )
else
@ceedling[BULLSEYE_SYM].generate_coverage_object_file(object.source, object.name)
end
end
rule(/#{BULLSEYE_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_EXECUTABLE}$/) do |bin_file|
@ceedling[:generator].generate_executable_file(
TOOLS_BULLSEYE_LINKER,
BULLSEYE_SYM,
bin_file.prerequisites,
bin_file.name,
@ceedling[:file_path_utils].form_test_build_map_filepath(bin_file.name))
end
rule(/#{BULLSEYE_RESULTS_PATH}\/#{'.+\\'+EXTENSION_TESTPASS}$/ => [
proc do |task_name|
@ceedling[:file_path_utils].form_test_executable_filepath(task_name)
end
]) do |test_result|
@ceedling[:generator].generate_test_results(TOOLS_BULLSEYE_FIXTURE, BULLSEYE_SYM, test_result.source, test_result.name)
end
rule(/#{BULLSEYE_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |dep|
@ceedling[:generator].generate_dependencies_file(
TOOLS_TEST_DEPENDENCIES_GENERATOR,
BULLSEYE_SYM,
dep.source,
File.join(BULLSEYE_BUILD_OUTPUT_PATH, File.basename(dep.source).ext(EXTENSION_OBJECT) ),
dep.name)
end
task :directories => [BULLSEYE_BUILD_OUTPUT_PATH, BULLSEYE_RESULTS_PATH, BULLSEYE_DEPENDENCIES_PATH, BULLSEYE_ARTIFACTS_PATH]
namespace BULLSEYE_SYM do
task :source_coverage => COLLECTION_ALL_SOURCE.pathmap("#{BULLSEYE_BUILD_OUTPUT_PATH}/%n#{@ceedling[:configurator].extension_object}")
desc "Run code coverage for all tests"
task :all => [:directories] do
@ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, BULLSEYE_SYM)
@ceedling[:configurator].restore_config
end
desc "Run single test w/ coverage ([*] real test or source file name, no path)."
task :* do
message = "\nOops! '#{BULLSEYE_ROOT_NAME}:*' isn't a real task. " +
"Use a real test or source file name (no path) in place of the wildcard.\n" +
"Example: rake #{BULLSEYE_ROOT_NAME}:foo.c\n\n"
@ceedling[:streaminator].stdout_puts( message )
end
desc "Run tests by matching regular expression pattern."
task :pattern, [:regex] => [:directories] do |t, args|
matches = []
COLLECTION_ALL_TESTS.each do |test|
matches << test if test =~ /#{args.regex}/
end
if (matches.size > 0)
@ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(matches, BULLSEYE_SYM, {:force_run => false})
@ceedling[:configurator].restore_config
else
@ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.")
end
end
desc "Run tests whose test path contains [dir] or [dir] substring."
task :path, [:dir] => [:directories] do |t, args|
matches = []
COLLECTION_ALL_TESTS.each do |test|
matches << test if File.dirname(test).include?(args.dir.gsub(/\\/, '/'))
end
if (matches.size > 0)
@ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(matches, BULLSEYE_SYM, {:force_run => false})
@ceedling[:configurator].restore_config
else
@ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.")
end
end
desc "Run code coverage for changed files"
task :delta => [:directories] do
@ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, BULLSEYE_SYM, {:force_run => false})
@ceedling[:configurator].restore_config
end
# use a rule to increase efficiency for large projects
# bullseye test tasks by regex
rule(/^#{BULLSEYE_TASK_ROOT}\S+$/ => [
proc do |task_name|
test = task_name.sub(/#{BULLSEYE_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" if not (test.start_with?(PROJECT_TEST_FILE_PREFIX))
@ceedling[:file_finder].find_test_from_file_path(test)
end
]) do |test|
@ceedling[:rake_wrapper][:directories].invoke
@ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config)
@ceedling[:test_invoker].setup_and_invoke([test.source], BULLSEYE_SYM)
@ceedling[:configurator].restore_config
end
end
if PROJECT_USE_DEEP_DEPENDENCIES
namespace REFRESH_SYM do
task BULLSEYE_SYM do
@ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config)
@ceedling[:test_invoker].refresh_deep_dependencies
@ceedling[:configurator].restore_config
end
end
end
namespace UTILS_SYM do
desc "Open Bullseye code coverage browser"
task BULLSEYE_SYM do
command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_BROWSER)
@ceedling[:tool_executor].exec(command[:line], command[:options])
end
end

View File

@ -0,0 +1,53 @@
---
:paths:
:bullseye_toolchain_include: []
:tools:
:bullseye_instrumentation:
:executable: covc
:arguments:
- '--file $': ENVIRONMENT_COVFILE
- -q
- ${1}
:bullseye_compiler:
:executable: gcc
:arguments:
- -g
- -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
- -I"$": COLLECTION_PATHS_BULLSEYE_TOOLCHAIN_INCLUDE
- -D$: COLLECTION_DEFINES_TEST_AND_VENDOR
- -DBULLSEYE_COMPILER
- -c "${1}"
- -o "${2}"
:bullseye_linker:
:executable: gcc
:arguments:
- ${1}
- -o ${2}
- -L$: PLUGINS_BULLSEYE_LIB_PATH
- -lcov
:bullseye_fixture:
:executable: ${1}
:bullseye_report_covsrc:
:executable: covsrc
:arguments:
- '--file $': ENVIRONMENT_COVFILE
- -q
- -w140
:bullseye_report_covfn:
:executable: covfn
:stderr_redirect: :auto
:arguments:
- '--file $': ENVIRONMENT_COVFILE
- --width 120
- --no-source
- '"${1}"'
:bullseye_browser:
:executable: CoverageBrowser
:background_exec: :auto
:optional: TRUE
:arguments:
- '"$"': ENVIRONMENT_COVFILE
...

View File

@ -0,0 +1,172 @@
require 'plugin'
require 'constants'
BULLSEYE_ROOT_NAME = 'bullseye'
BULLSEYE_TASK_ROOT = BULLSEYE_ROOT_NAME + ':'
BULLSEYE_SYM = BULLSEYE_ROOT_NAME.to_sym
BULLSEYE_BUILD_PATH = "#{PROJECT_BUILD_ROOT}/#{BULLSEYE_ROOT_NAME}"
BULLSEYE_BUILD_OUTPUT_PATH = "#{BULLSEYE_BUILD_PATH}/out"
BULLSEYE_RESULTS_PATH = "#{BULLSEYE_BUILD_PATH}/results"
BULLSEYE_DEPENDENCIES_PATH = "#{BULLSEYE_BUILD_PATH}/dependencies"
BULLSEYE_ARTIFACTS_PATH = "#{PROJECT_BUILD_ARTIFACTS_ROOT}/#{BULLSEYE_ROOT_NAME}"
BULLSEYE_IGNORE_SOURCES = ['unity', 'cmock', 'cexception']
class Bullseye < Plugin
def setup
@result_list = []
@environment = [ {:covfile => File.join( BULLSEYE_ARTIFACTS_PATH, 'test.cov' )} ]
@plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
@coverage_template_all = @ceedling[:file_wrapper].read(File.join(@plugin_root, 'assets/template.erb'))
end
def config
{
:project_test_build_output_path => BULLSEYE_BUILD_OUTPUT_PATH,
:project_test_results_path => BULLSEYE_RESULTS_PATH,
:project_test_dependencies_path => BULLSEYE_DEPENDENCIES_PATH,
:defines_test => DEFINES_TEST + ['CODE_COVERAGE'],
:collection_defines_test_and_vendor => COLLECTION_DEFINES_TEST_AND_VENDOR + ['CODE_COVERAGE']
}
end
def generate_coverage_object_file(source, object)
arg_hash = {:tool => TOOLS_BULLSEYE_INSTRUMENTATION, :context => BULLSEYE_SYM, :source => source, :object => object}
@ceedling[:plugin_manager].pre_compile_execute(arg_hash)
@ceedling[:streaminator].stdout_puts("Compiling #{File.basename(source)} with coverage...")
compile_command =
@ceedling[:tool_executor].build_command_line(
TOOLS_BULLSEYE_COMPILER,
source,
object,
@ceedling[:file_path_utils].form_test_build_list_filepath( object ) )
coverage_command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_INSTRUMENTATION, compile_command[:line] )
shell_result = @ceedling[:tool_executor].exec( coverage_command[:line], coverage_command[:options] )
arg_hash[:shell_result] = shell_result
@ceedling[:plugin_manager].post_compile_execute(arg_hash)
end
def post_test_fixture_execute(arg_hash)
result_file = arg_hash[:result_file]
if ((result_file =~ /#{BULLSEYE_RESULTS_PATH}/) and (not @result_list.include?(result_file)))
@result_list << arg_hash[:result_file]
end
end
def post_build
return if (not @ceedling[:task_invoker].invoked?(/^#{BULLSEYE_TASK_ROOT}/))
# test results
results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list)
hash = {
:header => BULLSEYE_ROOT_NAME.upcase,
:results => results
}
@ceedling[:plugin_reportinator].run_test_results_report(hash) do
message = ''
message = 'Unit test failures.' if (results[:counts][:failed] > 0)
message
end
# coverage results
return if (verify_coverage_file() == false)
if (@ceedling[:task_invoker].invoked?(/^#{BULLSEYE_TASK_ROOT}(all|delta)/))
command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_REPORT_COVSRC)
shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options])
report_coverage_results_all(shell_result[:output])
else
report_per_function_coverage_results(@ceedling[:test_invoker].sources)
end
end
def summary
return if (verify_coverage_file() == false)
result_list = @ceedling[:file_path_utils].form_pass_results_filelist( BULLSEYE_RESULTS_PATH, COLLECTION_ALL_TESTS )
# test results
# get test results for only those tests in our configuration and of those only tests with results on disk
hash = {
:header => BULLSEYE_ROOT_NAME.upcase,
:results => @ceedling[:plugin_reportinator].assemble_test_results(result_list, {:boom => false})
}
@ceedling[:plugin_reportinator].run_test_results_report(hash)
# coverage results
command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_REPORT_COVSRC)
shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options])
report_coverage_results_all(shell_result[:output])
end
private ###################################
def report_coverage_results_all(coverage)
results = {
:header => BULLSEYE_ROOT_NAME.upcase,
:coverage => {
:functions => nil,
:branches => nil
}
}
if (coverage =~ /^Total.*?=\s+([0-9]+)\%/)
results[:coverage][:functions] = $1.to_i
end
if (coverage =~ /^Total.*=\s+([0-9]+)\%\s*$/)
results[:coverage][:branches] = $1.to_i
end
@ceedling[:plugin_reportinator].run_report($stdout, @coverage_template_all, results)
end
def report_per_function_coverage_results(sources)
banner = @ceedling[:plugin_reportinator].generate_banner( "#{BULLSEYE_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" )
@ceedling[:streaminator].stdout_puts "\n" + banner
coverage_sources = sources.clone
coverage_sources.delete_if {|item| item =~ /#{CMOCK_MOCK_PREFIX}.+#{EXTENSION_SOURCE}$/}
coverage_sources.delete_if {|item| item =~ /#{BULLSEYE_IGNORE_SOURCES.join('|')}#{EXTENSION_SOURCE}$/}
coverage_sources.each do |source|
command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_REPORT_COVFN, source)
shell_results = @ceedling[:tool_executor].exec(command[:line], command[:options])
coverage_results = shell_results[:output].deep_clone
coverage_results.sub!(/.*\n.*\n/,'') # Remove the Bullseye tool banner
if (coverage_results =~ /warning cov814: report is empty/)
coverage_results = "WARNING: #{source} contains no coverage data!\n\n"
@ceedling[:streaminator].stdout_puts(coverage_results, Verbosity::COMPLAIN)
else
coverage_results += "\n"
@ceedling[:streaminator].stdout_puts(coverage_results)
end
end
end
def verify_coverage_file
exist = @ceedling[:file_wrapper].exist?( ENVIRONMENT_COVFILE )
if (!exist)
banner = @ceedling[:plugin_reportinator].generate_banner( "#{BULLSEYE_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" )
@ceedling[:streaminator].stdout_puts "\n" + banner + "\nNo coverage file.\n\n"
end
return exist
end
end
# end blocks always executed following rake run
END {
# cache our input configurations to use in comparison upon next execution
@ceedling[:cacheinator].cache_test_config( @ceedling[:setupinator].config_hash ) if (@ceedling[:task_invoker].invoked?(/^#{BULLSEYE_TASK_ROOT}/))
}

View File

View File

@ -0,0 +1,34 @@
---
:tools:
:gcov_compiler:
:executable: gcc
:arguments:
- -g
- -fprofile-arcs
- -ftest-coverage
- -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
- -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
- -D$: COLLECTION_DEFINES_TEST_AND_VENDOR
- -DGCOV_COMPILER
- -c "${1}"
- -o "${2}"
:gcov_linker:
:executable: gcc
:arguments:
- -fprofile-arcs
- -ftest-coverage
- ${1}
- -o ${2}
:gcov_fixture:
:executable: ${1}
:gcov_report:
:executable: gcov
:arguments:
- -n
- -p
- -b
- -o "$": GCOV_BUILD_OUTPUT_PATH
- "\"${1}\""
...

View File

@ -0,0 +1,152 @@
directory(GCOV_BUILD_OUTPUT_PATH)
directory(GCOV_RESULTS_PATH)
directory(GCOV_ARTIFACTS_PATH)
directory(GCOV_DEPENDENCIES_PATH)
CLEAN.include(File.join(GCOV_BUILD_OUTPUT_PATH, '*'))
CLEAN.include(File.join(GCOV_RESULTS_PATH, '*'))
CLEAN.include(File.join(GCOV_DEPENDENCIES_PATH, '*'))
CLOBBER.include(File.join(GCOV_BUILD_PATH, '**/*'))
rule(/#{GCOV_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |object|
if (File.basename(object.source) =~ /^(#{PROJECT_TEST_FILE_PREFIX}|#{CMOCK_MOCK_PREFIX}|#{GCOV_IGNORE_SOURCES.join('|')})/i)
@ceedling[:generator].generate_object_file(
TOOLS_GCOV_COMPILER,
GCOV_SYM,
object.source,
object.name,
@ceedling[:file_path_utils].form_test_build_list_filepath( object.name ) )
else
@ceedling[GCOV_SYM].generate_coverage_object_file(object.source, object.name)
end
end
rule(/#{GCOV_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_EXECUTABLE}$/) do |bin_file|
@ceedling[:generator].generate_executable_file(
TOOLS_GCOV_LINKER,
GCOV_SYM,
bin_file.prerequisites,
bin_file.name,
@ceedling[:file_path_utils].form_test_build_map_filepath(bin_file.name))
end
rule(/#{GCOV_RESULTS_PATH}\/#{'.+\\'+EXTENSION_TESTPASS}$/ => [
proc do |task_name|
@ceedling[:file_path_utils].form_test_executable_filepath(task_name)
end
]) do |test_result|
@ceedling[:generator].generate_test_results(TOOLS_GCOV_FIXTURE, GCOV_SYM, test_result.source, test_result.name)
end
rule(/#{GCOV_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [
proc do |task_name|
@ceedling[:file_finder].find_compilation_input_file(task_name)
end
]) do |dep|
@ceedling[:generator].generate_dependencies_file(
TOOLS_TEST_DEPENDENCIES_GENERATOR,
GCOV_SYM,
dep.source,
File.join(GCOV_BUILD_OUTPUT_PATH, File.basename(dep.source).ext(EXTENSION_OBJECT) ),
dep.name)
end
task :directories => [GCOV_BUILD_OUTPUT_PATH, GCOV_RESULTS_PATH, GCOV_DEPENDENCIES_PATH, GCOV_ARTIFACTS_PATH]
namespace GCOV_SYM do
task :source_coverage => COLLECTION_ALL_SOURCE.pathmap("#{GCOV_BUILD_OUTPUT_PATH}/%n#{@ceedling[:configurator].extension_object}")
desc "Run code coverage for all tests"
task :all => [:directories] do
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, GCOV_SYM)
@ceedling[:configurator].restore_config
end
desc "Run single test w/ coverage ([*] real test or source file name, no path)."
task :* do
message = "\nOops! '#{GCOV_ROOT_NAME}:*' isn't a real task. " +
"Use a real test or source file name (no path) in place of the wildcard.\n" +
"Example: rake #{GCOV_ROOT_NAME}:foo.c\n\n"
@ceedling[:streaminator].stdout_puts( message )
end
desc "Run tests by matching regular expression pattern."
task :pattern, [:regex] => [:directories] do |t, args|
matches = []
COLLECTION_ALL_TESTS.each do |test|
matches << test if test =~ /#{args.regex}/
end
if (matches.size > 0)
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(matches, GCOV_SYM, {:force_run => false})
@ceedling[:configurator].restore_config
else
@ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.")
end
end
desc "Run tests whose test path contains [dir] or [dir] substring."
task :path, [:dir] => [:directories] do |t, args|
matches = []
COLLECTION_ALL_TESTS.each do |test|
matches << test if File.dirname(test).include?(args.dir.gsub(/\\/, '/'))
end
if (matches.size > 0)
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(matches, GCOV_SYM, {:force_run => false})
@ceedling[:configurator].restore_config
else
@ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.")
end
end
desc "Run code coverage for changed files"
task :delta => [:directories] do
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, GCOV_SYM, {:force_run => false})
@ceedling[:configurator].restore_config
end
# use a rule to increase efficiency for large projects
# gcov test tasks by regex
rule(/^#{GCOV_TASK_ROOT}\S+$/ => [
proc do |task_name|
test = task_name.sub(/#{GCOV_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" if not (test.start_with?(PROJECT_TEST_FILE_PREFIX))
@ceedling[:file_finder].find_test_from_file_path(test)
end
]) do |test|
@ceedling[:rake_wrapper][:directories].invoke
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].setup_and_invoke([test.source], GCOV_SYM)
@ceedling[:configurator].restore_config
end
end
if PROJECT_USE_DEEP_DEPENDENCIES
namespace REFRESH_SYM do
task GCOV_SYM do
@ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config)
@ceedling[:test_invoker].refresh_deep_dependencies
@ceedling[:configurator].restore_config
end
end
end

View File

@ -0,0 +1,128 @@
require 'plugin'
require 'constants'
GCOV_ROOT_NAME = 'gcov'
GCOV_TASK_ROOT = GCOV_ROOT_NAME + ':'
GCOV_SYM = GCOV_ROOT_NAME.to_sym
GCOV_BUILD_PATH = "#{PROJECT_BUILD_ROOT}/#{GCOV_ROOT_NAME}"
GCOV_BUILD_OUTPUT_PATH = "#{GCOV_BUILD_PATH}/out"
GCOV_RESULTS_PATH = "#{GCOV_BUILD_PATH}/results"
GCOV_DEPENDENCIES_PATH = "#{GCOV_BUILD_PATH}/dependencies"
GCOV_ARTIFACTS_PATH = "#{PROJECT_BUILD_ARTIFACTS_ROOT}/#{GCOV_ROOT_NAME}"
GCOV_IGNORE_SOURCES = ['unity', 'cmock', 'cexception']
class Gcov < Plugin
attr_reader :config
def setup
@result_list = []
@config = {
:project_test_build_output_path => GCOV_BUILD_OUTPUT_PATH,
:project_test_results_path => GCOV_RESULTS_PATH,
:project_test_dependencies_path => GCOV_DEPENDENCIES_PATH,
:defines_test => DEFINES_TEST + ['CODE_COVERAGE'],
:collection_defines_test_and_vendor => COLLECTION_DEFINES_TEST_AND_VENDOR + ['CODE_COVERAGE']
}
@coverage_template_all = @ceedling[:file_wrapper].read( File.join( PLUGINS_GCOV_PATH, 'template.erb') )
end
def generate_coverage_object_file(source, object)
compile_command =
@ceedling[:tool_executor].build_command_line(
TOOLS_GCOV_COMPILER,
source,
object,
@ceedling[:file_path_utils].form_test_build_list_filepath( object ) )
@ceedling[:streaminator].stdout_puts("Compiling #{File.basename(source)} with coverage...")
@ceedling[:tool_executor].exec( compile_command[:line], compile_command[:options] )
end
def post_test_fixture_execute(arg_hash)
result_file = arg_hash[:result_file]
if ((result_file =~ /#{GCOV_RESULTS_PATH}/) and (not @result_list.include?(result_file)))
@result_list << arg_hash[:result_file]
end
end
def post_build
return if (not @ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/))
# test results
results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list)
hash = {
:header => GCOV_ROOT_NAME.upcase,
:results => results
}
@ceedling[:plugin_reportinator].run_test_results_report(hash) do
message = ''
message = 'Unit test failures.' if (results[:counts][:failed] > 0)
message
end
if (@ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}(all|delta)/))
report_coverage_results_summary(@ceedling[:test_invoker].sources)
else
report_per_file_coverage_results(@ceedling[:test_invoker].sources)
end
end
def summary
result_list = @ceedling[:file_path_utils].form_pass_results_filelist( GCOV_RESULTS_PATH, COLLECTION_ALL_TESTS )
# test results
# get test results for only those tests in our configuration and of those only tests with results on disk
hash = {
:header => GCOV_ROOT_NAME.upcase,
:results => @ceedling[:plugin_reportinator].assemble_test_results(result_list, {:boom => false})
}
@ceedling[:plugin_reportinator].run_test_results_report(hash)
# coverage results
# command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORT_COVSRC)
# shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options])
# report_coverage_results_all(shell_result[:output])
end
private ###################################
def report_coverage_results_summary(sources)
end
def report_per_file_coverage_results(sources)
banner = @ceedling[:plugin_reportinator].generate_banner "#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY"
@ceedling[:streaminator].stdout_puts "\n" + banner
coverage_sources = sources.clone
coverage_sources.delete_if {|item| item =~ /#{CMOCK_MOCK_PREFIX}.+#{EXTENSION_SOURCE}$/}
coverage_sources.delete_if {|item| item =~ /#{GCOV_IGNORE_SOURCES.join('|')}#{EXTENSION_SOURCE}$/}
coverage_sources.each do |source|
basename = File.basename(source)
command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORT, basename)
shell_results = @ceedling[:tool_executor].exec(command[:line], command[:options])
coverage_results = shell_results[:output]
if (coverage_results.strip =~ /(File\s+'#{Regexp.escape(source)}'.+$)/m)
report = ((($1.lines.to_a)[1..-1])).map{|line| basename + ' ' + line}.join('')
@ceedling[:streaminator].stdout_puts(report + "\n\n")
end
end
end
end
# end blocks always executed following rake run
END {
# cache our input configurations to use in comparison upon next execution
@ceedling[:cacheinator].cache_test_config( @ceedling[:setupinator].config_hash ) if (@ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/))
}

View File

View File

@ -0,0 +1,15 @@
% function_string = hash[:coverage][:functions].to_s
% branch_string = hash[:coverage][:branches].to_s
% format_string = "%#{[function_string.length, branch_string.length].max}i"
<%=@ceedling[:plugin_reportinator].generate_banner("#{BULLSEYE_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY")%>
% if (!hash[:coverage][:functions].nil?)
FUNCTIONS: <%=sprintf(format_string, hash[:coverage][:functions])%>%
% else
FUNCTIONS: none
% end
% if (!hash[:coverage][:branches].nil?)
BRANCHES: <%=sprintf(format_string, hash[:coverage][:branches])%>%
% else
BRANCHES: none
% end

Some files were not shown because too many files have changed in this diff Show More