Skip to content

Commit d72c378

Browse files
author
James Foster
authored
Build a shared library for use with each test (#294)
* Speed up tests by building a shared library with everything but the tests and reusing it for each test. * Add meaningful comments and remove commented-out code. * Set LD_LIBRARY_PATH to directory with shared library. * Use constant variables for names and fix problem where library was not used. * Separate rubocop from general testing in LInux. * Rename GitHub Action workflow job. * The path seems to be structured differently in some tests. * Try with .dll as the library name. * Create standalone function to build the shared library. * rename function and give the right number of parameters * Upshift `itoa()` result so that Windows result matches expected values.
1 parent 359e0f6 commit d72c378

File tree

10 files changed

+179
-48
lines changed

10 files changed

+179
-48
lines changed

Diff for: .github/workflows/linux.yaml

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: linux
44
on: [push, pull_request]
55

66
jobs:
7-
"unittest_lint_sampleproject":
7+
"rubocop":
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v2
@@ -17,7 +17,31 @@ jobs:
1717
bundle install
1818
bundle exec rubocop --version
1919
bundle exec rubocop -D .
20+
21+
"rspec":
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v2
25+
- uses: ruby/setup-ruby@v1
26+
with:
27+
ruby-version: 2.6
28+
- name: Check style, functionality, and usage
29+
run: |
30+
g++ -v
31+
bundle install
2032
bundle exec rspec --backtrace
33+
34+
"TestSomething":
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v2
38+
- uses: ruby/setup-ruby@v1
39+
with:
40+
ruby-version: 2.6
41+
- name: TestSomething
42+
run: |
43+
g++ -v
44+
bundle install
2145
cd SampleProjects/TestSomething
2246
bundle install
2347
bundle exec arduino_ci.rb

Diff for: .github/workflows/macos.yaml

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: macos
44
on: [push, pull_request]
55

66
jobs:
7-
"unittest_lint_sampleproject":
7+
"rubocop":
88
runs-on: macos-latest
99
steps:
1010
- uses: actions/checkout@v2
@@ -17,7 +17,31 @@ jobs:
1717
bundle install
1818
bundle exec rubocop --version
1919
bundle exec rubocop -D .
20+
21+
"rspec":
22+
runs-on: macos-latest
23+
steps:
24+
- uses: actions/checkout@v2
25+
- uses: ruby/setup-ruby@v1
26+
with:
27+
ruby-version: 2.6
28+
- name: Check style, functionality, and usage
29+
run: |
30+
g++ -v
31+
bundle install
2032
bundle exec rspec --backtrace
33+
34+
"TestSomething":
35+
runs-on: macos-latest
36+
steps:
37+
- uses: actions/checkout@v2
38+
- uses: ruby/setup-ruby@v1
39+
with:
40+
ruby-version: 2.6
41+
- name: TestSomething
42+
run: |
43+
g++ -v
44+
bundle install
2145
cd SampleProjects/TestSomething
2246
bundle install
2347
bundle exec arduino_ci.rb

Diff for: .github/workflows/windows.yaml

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: windows
44
on: [push, pull_request]
55

66
jobs:
7-
"unittest_lint_sampleproject":
7+
"rubocop_and_rspec":
88
runs-on: windows-latest
99
steps:
1010
- uses: actions/checkout@v2
@@ -17,7 +17,20 @@ jobs:
1717
bundle install
1818
bundle exec rubocop --version
1919
bundle exec rubocop -D .
20+
echo "done with Rubocop (See https://github.com/Arduino-CI/arduino_ci/issues/315)"
2021
bundle exec rspec --backtrace
22+
23+
"TestSomething":
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v2
27+
- uses: ruby/setup-ruby@v1
28+
with:
29+
ruby-version: 2.6
30+
- name: TestSomething
31+
run: |
32+
g++ -v
33+
bundle install
2134
cd SampleProjects/TestSomething
2235
bundle install
2336
bundle exec arduino_ci.rb

Diff for: .gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ Gemfile.lock
88
/spec/reports/
99
vendor
1010
*.gem
11+
.arduino_ci
1112

1213
# rspec failure tracking
1314
.rspec_status
1415

1516
# C++ stuff
1617
*.bin
1718
*.bin.dSYM
19+
*.so
20+
*.so.dSYM
21+
.vscode

Diff for: CHANGELOG.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
- Support for `dtostrf()`
1515

1616
### Changed
17-
- Fix copy/paste error to allow additional warnings for a platform
18-
- Properly report compile errors in GitHub Actions (#296)
17+
- We now compile a shared library to be used for each test.
1918
- Put build artifacts in a separate directory to reduce clutter.
2019
- Replace `#define yield() _NOP()` with `inline void yield() { _NOP(); }` so that other code can define a `yield()` function.
2120
- Update .gitattributes so we have consistent line endings
2221
- Change 266 files from CRLF to LF.
2322
- Run tests on push as well as on a pull request so developers can see impact
24-
- Apply "rule of three" to Client copy constructor and copy assignment operator
2523

2624
### Deprecated
2725

2826
### Removed
2927

3028
### Fixed
29+
- Properly report compile errors in GitHub Actions.
30+
- Fix copy/paste error to allow additional warnings for a platform
31+
- Apply "rule of three" to Client copy constructor and copy assignment operator
3132

3233
### Security
3334

@@ -397,7 +398,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
397398

398399
### Fixed
399400
- Malformed YAML (duplicate unittests section) now has no duplicate section
400-
- arduino_ci_remote.rb script now has correct arguments in build_for_test_with_configuration
401+
- arduino_ci_remote.rb script now has correct arguments in build_for_test
401402

402403

403404
## [0.1.8] - 2018-04-03

Diff for: SampleProjects/TestSomething/test/stdlib.cpp

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#include <ArduinoUnitTests.h>
22
#include <Arduino.h>
3+
#include <ctype.h>
4+
#include <string.h>
35

46
#define ARRAY_SIZEOF(a) ( sizeof(a) / sizeof((a)[0]) )
57

68
unittest(library_tests_itoa)
79
{
810
char buf[32];
9-
const char *result;
1011
struct {
1112
int value;
1213
const char *expected;
@@ -26,19 +27,22 @@ unittest(library_tests_itoa)
2627
};
2728

2829
for (int i = 0; i < ARRAY_SIZEOF(table); i++) {
29-
result = itoa(table[i].value, buf, table[i].base);
30-
assertEqual(table[i].expected, result);
30+
itoa(table[i].value, buf, table[i].base);
31+
for (int j = 0; j < strlen(buf); ++j) {
32+
buf[j] = toupper(buf[j]);
33+
}
34+
assertEqual(table[i].expected, buf);
3135
}
3236

33-
// While only bases 2, 8, 10 and 16 are of real interest, lets test that all
37+
// While only bases 2, 8, 10 and 16 are of real interest, let's test that all
3438
// bases at least produce expected output for a few test points simple to test.
3539
for (int base = 2; base <= 16; base++) {
36-
result = itoa(0, buf, base);
37-
assertEqual("0", result);
38-
result = itoa(1, buf, base);
39-
assertEqual("1", result);
40-
result = itoa(base, buf, base);
41-
assertEqual("10", result);
40+
itoa(0, buf, base);
41+
assertEqual("0", buf);
42+
itoa(1, buf, base);
43+
assertEqual("1", buf);
44+
itoa(base, buf, base);
45+
assertEqual("10", buf);
4246
}
4347

4448
}

Diff for: exe/arduino_ci.rb

+27-9
Original file line numberDiff line numberDiff line change
@@ -421,16 +421,16 @@ def perform_unit_tests(cpp_library, file_config)
421421

422422
platforms.each do |p|
423423
puts
424-
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
425-
unittest_name = unittest_path.basename.to_s
426-
compilers.each do |gcc_binary|
424+
compilers.each do |gcc_binary|
425+
# before compiling the tests, build a shared library of everything except the test code
426+
next unless build_shared_library(gcc_binary, p, config, cpp_library)
427+
428+
# now build and run each test using the shared library build above
429+
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
430+
unittest_name = unittest_path.basename.to_s
431+
puts "--------------------------------------------------------------------------------"
427432
attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
428-
exe = cpp_library.build_for_test_with_configuration(
429-
unittest_path,
430-
config.aux_libraries_for_unittest,
431-
gcc_binary,
432-
config.gcc_config(p)
433-
)
433+
exe = cpp_library.build_for_test(unittest_path, gcc_binary)
434434
puts
435435
unless exe
436436
puts "Last command: #{cpp_library.last_cmd}"
@@ -445,6 +445,24 @@ def perform_unit_tests(cpp_library, file_config)
445445
end
446446
end
447447

448+
def build_shared_library(gcc_binary, platform, config, cpp_library)
449+
attempt_multiline("Build shared library with #{gcc_binary} for #{platform}") do
450+
exe = cpp_library.build_shared_library(
451+
config.aux_libraries_for_unittest,
452+
gcc_binary,
453+
config.gcc_config(platform)
454+
)
455+
puts
456+
unless exe
457+
puts "Last command: #{cpp_library.last_cmd}"
458+
puts cpp_library.last_out
459+
puts cpp_library.last_err
460+
return false
461+
end
462+
return true
463+
end
464+
end
465+
448466
def perform_example_compilation_tests(cpp_library, config)
449467
phase("Compilation of example sketches")
450468
if @cli_options[:skip_compilation]

Diff for: lib/arduino_ci/cpp_library.rb

+57-16
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
require "arduino_ci/host"
33
require 'pathname'
44
require 'shellwords'
5+
require 'os'
56

67
HPP_EXTENSIONS = [".hpp", ".hh", ".h", ".hxx", ".h++"].freeze
78
CPP_EXTENSIONS = [".cpp", ".cc", ".c", ".cxx", ".c++"].freeze
89
CI_CPP_DIR = Pathname.new(__dir__).parent.parent + "cpp"
910
ARDUINO_HEADER_DIR = CI_CPP_DIR + "arduino"
1011
UNITTEST_HEADER_DIR = CI_CPP_DIR + "unittest"
12+
LIBRARY_NAME = "arduino".freeze
13+
BUILD_DIR = "#{Dir.pwd}/.arduino_ci".freeze # hide build artifacts
1114

1215
module ArduinoCI
1316

@@ -464,16 +467,15 @@ def flag_args(ci_gcc_config)
464467
ci_gcc_config[:flags]
465468
end
466469

467-
# All GCC command line args for building any unit test
470+
# All non-CPP GCC command line args for building any unit test.
471+
# We leave out the CPP files so they can be included or not
472+
# depending on whether we are building a shared library.
468473
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
469474
# @param ci_gcc_config [Hash] The GCC config object
470475
# @return [Array<String>] GCC command-line flags
471476
def test_args(aux_libraries, ci_gcc_config)
472477
# TODO: something with libraries?
473478
ret = include_args(aux_libraries)
474-
ret += cpp_files_arduino.map(&:to_s)
475-
ret += cpp_files_unittest.map(&:to_s)
476-
ret += cpp_files.map(&:to_s)
477479
unless ci_gcc_config.nil?
478480
cgc = ci_gcc_config
479481
ret = feature_args(cgc) + warning_args(cgc) + define_args(cgc) + flag_args(cgc) + ret
@@ -486,18 +488,52 @@ def test_args(aux_libraries, ci_gcc_config)
486488
# The dependent libraries configuration is appended with data from library.properties internal to the library under test
487489
#
488490
# @param test_file [Pathname] The path to the file containing the unit tests
491+
# @param gcc_binary [String] name of a compiler
492+
# @return [Pathname] path to the compiled test executable
493+
def build_for_test(test_file, gcc_binary)
494+
executable = Pathname.new("#{BUILD_DIR}/#{test_file.basename}.bin").expand_path
495+
File.delete(executable) if File.exist?(executable)
496+
arg_sets = ["-std=c++0x", "-o", executable.to_s, "-L#{BUILD_DIR}", "-DARDUINO=100"]
497+
if libasan?(gcc_binary)
498+
arg_sets << [ # Stuff to help with dynamic memory mishandling
499+
"-g", "-O1",
500+
"-fno-omit-frame-pointer",
501+
"-fno-optimize-sibling-calls",
502+
"-fsanitize=address"
503+
]
504+
end
505+
arg_sets << @test_args
506+
arg_sets << [test_file.to_s, "-l#{LIBRARY_NAME}"]
507+
args = arg_sets.flatten(1)
508+
return nil unless run_gcc(gcc_binary, *args)
509+
510+
artifacts << executable
511+
executable
512+
end
513+
514+
# build a shared library to be used by each test
515+
#
516+
# The dependent libraries configuration is appended with data from library.properties internal to the library under test
517+
#
489518
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
519+
# @param gcc_binary [String] name of a compiler
490520
# @param ci_gcc_config [Hash] The GCC config object
491521
# @return [Pathname] path to the compiled test executable
492-
def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_gcc_config)
493-
base = test_file.basename
494-
# hide build artifacts
495-
build_dir = '.arduino_ci'
496-
Dir.mkdir build_dir unless File.exist?(build_dir)
497-
executable = Pathname.new("#{build_dir}/unittest_#{base}.bin").expand_path
522+
def build_shared_library(aux_libraries, gcc_binary, ci_gcc_config)
523+
Dir.mkdir BUILD_DIR unless File.exist?(BUILD_DIR)
524+
if OS.windows?
525+
flag = ENV["PATH"].include? ";"
526+
ENV["PATH"] = BUILD_DIR + (flag ? ";" : ":") + ENV["PATH"] unless ENV["PATH"].include? BUILD_DIR
527+
suffix = "dll"
528+
else
529+
ENV["LD_LIBRARY_PATH"] = BUILD_DIR
530+
suffix = "so"
531+
end
532+
full_lib_name = "#{BUILD_DIR}/lib#{LIBRARY_NAME}.#{suffix}"
533+
executable = Pathname.new(full_lib_name).expand_path
498534
File.delete(executable) if File.exist?(executable)
499-
arg_sets = []
500-
arg_sets << ["-std=c++0x", "-o", executable.to_s, "-DARDUINO=100"]
535+
arg_sets = ["-std=c++0x", "-shared", "-fPIC", "-Wl,-undefined,dynamic_lookup",
536+
"-o", executable.to_s, "-L#{BUILD_DIR}", "-DARDUINO=100"]
501537
if libasan?(gcc_binary)
502538
arg_sets << [ # Stuff to help with dynamic memory mishandling
503539
"-g", "-O1",
@@ -509,10 +545,15 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g
509545

510546
# combine library.properties defs (if existing) with config file.
511547
# TODO: as much as I'd like to rely only on the properties file(s), I think that would prevent testing 1.0-spec libs
512-
full_dependencies = all_arduino_library_dependencies!(aux_libraries)
513-
arg_sets << test_args(full_dependencies, ci_gcc_config)
514-
arg_sets << cpp_files_libraries(full_dependencies).map(&:to_s)
515-
arg_sets << [test_file.to_s]
548+
# the following two take some time, so are cached when we build the shared library
549+
@full_dependencies = all_arduino_library_dependencies!(aux_libraries)
550+
@test_args = test_args(@full_dependencies, ci_gcc_config) # build full set of include directories to be cached for later
551+
552+
arg_sets << @test_args
553+
arg_sets << cpp_files_arduino.map(&:to_s) # Arduino.cpp, Godmode.cpp, and stdlib.cpp
554+
arg_sets << cpp_files_unittest.map(&:to_s) # ArduinoUnitTests.cpp
555+
arg_sets << cpp_files.map(&:to_s) # CPP files for the primary application library under test
556+
arg_sets << cpp_files_libraries(@full_dependencies).map(&:to_s) # CPP files for all the libraries we depend on
516557
args = arg_sets.flatten(1)
517558
return nil unless run_gcc(gcc_binary, *args)
518559

0 commit comments

Comments
 (0)