Skip to content

Commit 72bb3ff

Browse files
committed
Fix Valgrind plugin and add tests
This commit fixes the Valgrind plugin by removing some deprecated functions and updating some function signatures. This commit also adds tests to serve as regression testing to prevent the Valgrind plugin from being broken by future refactoring changes. Signed-off-by: James Raphael Tiovalen <[email protected]>
1 parent dce63fd commit 72bb3ff

File tree

7 files changed

+287
-21
lines changed

7 files changed

+287
-21
lines changed

assets/project_as_gem.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#- dependencies # automatically fetch 3rd party libraries, etc.
6464
#- subprojects # managing builds and test for static libraries
6565
#- fake_function_framework # use FFF instead of CMock
66+
#- valgrind # detect memory management and threading bugs using valgrind. Requires valgrind for your platform
6667

6768
# Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired)
6869
#- report_build_warnings_log
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# =========================================================================
2+
# Ceedling - Test-Centered Build System for C
3+
# ThrowTheSwitch.org
4+
# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
5+
# SPDX-License-Identifier: MIT
6+
# =========================================================================
7+
8+
---
9+
10+
# Enable valgrind plugin
11+
:plugins:
12+
:enabled:
13+
- valgrind

examples/temp_sensor/project.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#- dependencies # automatically fetch 3rd party libraries, etc.
6666
#- subprojects # managing builds and test for static libraries
6767
#- fake_function_framework # use FFF instead of CMock
68+
#- valgrind # detect memory management and threading bugs using valgrind. Requires valgrind for your platform
6869

6970
# Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired)
7071
#- report_build_warnings_log

plugins/valgrind/config/defaults_valgrind.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
:executable => ENV['VALGRIND'].nil? ? FilePathUtils.os_executable_ext('valgrind').freeze : ENV['VALGRIND'].split[0],
44
:name => 'default_valgrind'.freeze,
55
:stderr_redirect => StdErrRedirect::NONE.freeze,
6-
:background_exec => BackgroundExec::NONE.freeze,
76
:optional => false.freeze,
87
:arguments => [
98
"--leak-check=full".freeze,

plugins/valgrind/valgrind.rake

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ CLOBBER.include(File.join(VALGRIND_BUILD_PATH, '**/*'))
77
task directories: [VALGRIND_BUILD_OUTPUT_PATH]
88

99
namespace VALGRIND_SYM do
10-
task source_coverage: COLLECTION_ALL_SOURCE.pathmap("#{VALGRIND_BUILD_OUTPUT_PATH}/%n#{@ceedling[:configurator].extension_object}")
11-
1210
desc 'Run Valgrind for all tests'
13-
task all: [:test_deps] do
11+
task all: [:prepare] do
1412
@ceedling[:configurator].replace_flattened_config(@ceedling[VALGRIND_SYM].config)
1513
COLLECTION_ALL_TESTS.each do |test|
16-
executable = @ceedling[:file_path_utils].form_test_executable_filepath(test)
14+
executable = @ceedling[:file_path_utils].form_test_executable_filepath(File.join(VALGRIND_BUILD_OUTPUT_PATH, File.basename(test, @ceedling[:configurator].extension_source)), test)
1715
command = @ceedling[:tool_executor].build_command_line(TOOLS_VALGRIND, [], executable)
18-
@ceedling[:streaminator].stdout_puts("\nINFO: #{command[:line]}\n\n")
19-
@ceedling[:tool_executor].exec(command[:line], command[:options])
16+
@ceedling[:loginator].log("\nINFO: #{command[:line]}\n\n")
17+
shell_result = @ceedling[:tool_executor].exec(command)
18+
@ceedling[:loginator].log("#{shell_result[:output]}\n")
2019
end
21-
@ceedling[:configurator].restore_config
2220
end
2321

2422
desc 'Run Valgrind for a single test or executable ([*] real test or source file name, no path).'
@@ -27,23 +25,71 @@ namespace VALGRIND_SYM do
2725
"Use a real test or source file name (no path) in place of the wildcard.\n" \
2826
"Example: rake #{VALGRIND_ROOT_NAME}:foo.c\n\n"
2927

30-
@ceedling[:streaminator].stdout_puts(message)
28+
@ceedling[:loginator].log( message, Verbosity::ERRORS )
3129
end
3230

33-
# use a rule to increase efficiency for large projects
34-
# valgrind test tasks by regex
35-
rule(/^#{VALGRIND_TASK_ROOT}\S+$/ => [
31+
desc 'Run Valgrind for tests by matching regular expression pattern.'
32+
task :pattern, [:regex] => [:prepare] do |_t, args|
33+
matches = []
34+
35+
COLLECTION_ALL_TESTS.each do |test|
36+
matches << test if test =~ /#{args.regex}/
37+
end
38+
39+
if !matches.empty?
40+
@ceedling[:configurator].replace_flattened_config(@ceedling[VALGRIND_SYM].config)
41+
matches.each do |test|
42+
executable = @ceedling[:file_path_utils].form_test_executable_filepath(File.join(VALGRIND_BUILD_OUTPUT_PATH, File.basename(test, @ceedling[:configurator].extension_source)), test)
43+
command = @ceedling[:tool_executor].build_command_line(TOOLS_VALGRIND, [], executable)
44+
@ceedling[:loginator].log("\nINFO: #{command[:line]}\n\n")
45+
shell_result = @ceedling[:tool_executor].exec(command)
46+
@ceedling[:loginator].log("#{shell_result[:output]}\n")
47+
end
48+
else
49+
@ceedling[:loginator].log("\nFound no tests matching pattern /#{args.regex}/.")
50+
end
51+
end
52+
53+
desc 'Run Valgrind for tests whose test path contains [dir] or [dir] substring.'
54+
task :path, [:dir] => [:prepare] do |_t, args|
55+
matches = []
56+
57+
COLLECTION_ALL_TESTS.each do |test|
58+
matches << test if File.dirname(test).include?(args.dir.tr('\\', '/'))
59+
end
60+
61+
if !matches.empty?
62+
@ceedling[:configurator].replace_flattened_config(@ceedling[VALGRIND_SYM].config)
63+
matches.each do |test|
64+
executable = @ceedling[:file_path_utils].form_test_executable_filepath(File.join(VALGRIND_BUILD_OUTPUT_PATH, File.basename(test, @ceedling[:configurator].extension_source)), test)
65+
command = @ceedling[:tool_executor].build_command_line(TOOLS_VALGRIND, [], executable)
66+
@ceedling[:loginator].log("\nINFO: #{command[:line]}\n\n")
67+
shell_result = @ceedling[:tool_executor].exec(command)
68+
@ceedling[:loginator].log("#{shell_result[:output]}\n")
69+
end
70+
else
71+
@ceedling[:loginator].log( 'Found no tests including the given path or path component', Verbosity::ERRORS )
72+
end
73+
end
74+
75+
# Use a rule to increase efficiency for large projects
76+
rule(/^#{VALGRIND_TASK_ROOT}\S+$/ => [ # valgrind test tasks by regex
3677
proc do |task_name|
37-
test = task_name.sub(/#{VALGRIND_TASK_ROOT}/, '')
38-
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" unless test.start_with?(PROJECT_TEST_FILE_PREFIX)
39-
@ceedling[:file_finder].find_test_from_file_path(test)
78+
# Yield clean test name => Strip the task string, remove Rake test task prefix, and remove any code file extension
79+
test = task_name.strip().sub(/^#{VALGRIND_TASK_ROOT}/, '').chomp( EXTENSION_SOURCE )
80+
81+
# Ensure the test name begins with a test name prefix
82+
test = PROJECT_TEST_FILE_PREFIX + test if not (test.start_with?( PROJECT_TEST_FILE_PREFIX ))
83+
84+
# Provide the filepath for the target test task back to the Rake task
85+
@ceedling[:file_finder].find_test_file_from_name( test )
4086
end
41-
]) do test
87+
]) do |test|
4288
@ceedling[:configurator].replace_flattened_config(@ceedling[VALGRIND_SYM].config)
43-
executable = @ceedling[:file_path_utils].form_test_executable_filepath(test.source)
89+
executable = @ceedling[:file_path_utils].form_test_executable_filepath(File.join(VALGRIND_BUILD_OUTPUT_PATH, File.basename(test.source, @ceedling[:configurator].extension_source)), test.source)
4490
command = @ceedling[:tool_executor].build_command_line(TOOLS_VALGRIND, [], executable)
45-
@ceedling[:streaminator].stdout_puts("\nINFO: #{command[:line]}\n\n")
46-
@ceedling[:tool_executor].exec(command[:line], command[:options])
47-
@ceedling[:configurator].restore_config
91+
@ceedling[:loginator].log("\nINFO: #{command[:line]}\n\n")
92+
shell_result = @ceedling[:tool_executor].exec(command)
93+
@ceedling[:loginator].log("#{shell_result[:output]}\n")
4894
end
49-
end
95+
end
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# =========================================================================
2+
# Ceedling - Test-Centered Build System for C
3+
# ThrowTheSwitch.org
4+
# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
5+
# SPDX-License-Identifier: MIT
6+
# =========================================================================
7+
8+
require 'spec_system_helper'
9+
require 'valgrind/valgrind_test_cases_spec'
10+
11+
describe "Ceedling" do
12+
describe "Valgrind" do
13+
include CeedlingTestCases
14+
include ValgrindTestCases
15+
before :all do
16+
determine_reports_to_test
17+
@c = SystemContext.new
18+
@c.deploy_gem
19+
end
20+
21+
after :all do
22+
@c.done!
23+
end
24+
25+
before { @proj_name = "fake_project" }
26+
after { @c.with_context { FileUtils.rm_rf @proj_name } }
27+
28+
describe "basic operations" do
29+
before do
30+
@c.with_context do
31+
`bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1`
32+
end
33+
end
34+
35+
it { can_test_projects_with_valgrind_with_success }
36+
it { can_test_projects_with_valgrind_with_fail }
37+
it { can_test_projects_with_valgrind_with_compile_error }
38+
it { can_fetch_project_help_for_valgrind }
39+
end
40+
41+
describe "command: `ceedling example temp_sensor`" do
42+
describe "temp_sensor" do
43+
before do
44+
@c.with_context do
45+
output = `bundle exec ruby -S ceedling example temp_sensor 2>&1`
46+
expect(output).to match(/created/)
47+
48+
Dir.chdir "temp_sensor" do
49+
@output = `bundle exec ruby -S ceedling --mixin=add_valgrind test:all 2>&1`
50+
expect(@output).to match(/TESTED:\s+51/)
51+
expect(@output).to match(/PASSED:\s+51/)
52+
end
53+
end
54+
end
55+
56+
it "should be testable" do
57+
@c.with_context do
58+
Dir.chdir "temp_sensor" do
59+
@output = `bundle exec ruby -S ceedling --mixin=add_valgrind valgrind:all 2>&1`
60+
expect(@output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
61+
expect(@output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
62+
end
63+
end
64+
end
65+
66+
it "should be able to test a single module (it should INHERIT file-specific flags)" do
67+
@c.with_context do
68+
Dir.chdir "temp_sensor" do
69+
@output = `bundle exec ruby -S ceedling --mixin=add_valgrind valgrind:TemperatureCalculator 2>&1`
70+
expect(@output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
71+
expect(@output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
72+
end
73+
end
74+
end
75+
76+
it "should be able to test multiple files matching a pattern" do
77+
@c.with_context do
78+
Dir.chdir "temp_sensor" do
79+
@output = `bundle exec ruby -S ceedling --mixin=add_valgrind valgrind:pattern[Temp] 2>&1`
80+
expect(@output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
81+
expect(@output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
82+
end
83+
end
84+
end
85+
86+
it "should be able to test all files matching in a path" do
87+
@c.with_context do
88+
Dir.chdir "temp_sensor" do
89+
@output = `bundle exec ruby -S ceedling --mixin=add_valgrind valgrind:path[adc] 2>&1`
90+
expect(@output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
91+
expect(@output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
92+
end
93+
end
94+
end
95+
96+
it "should be able to test specific test cases in a file" do
97+
@c.with_context do
98+
Dir.chdir "temp_sensor" do
99+
@output = `bundle exec ruby -S ceedling --mixin=add_valgrind valgrind:path[adc] --test-case="RunShouldNot" 2>&1`
100+
expect(@output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
101+
expect(@output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
102+
end
103+
end
104+
end
105+
end
106+
end
107+
end
108+
end
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# =========================================================================
2+
# Ceedling - Test-Centered Build System for C
3+
# ThrowTheSwitch.org
4+
# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
5+
# SPDX-License-Identifier: MIT
6+
# =========================================================================
7+
8+
require 'fileutils'
9+
require 'tmpdir'
10+
require 'yaml'
11+
require 'spec_system_helper'
12+
require 'pp'
13+
14+
module ValgrindTestCases
15+
def determine_reports_to_test
16+
@valgrind_reports = []
17+
18+
begin
19+
`valgrind --version 2>&1`
20+
@valgrind_reports << :valgrind if $?.exitstatus == 0
21+
rescue
22+
puts "No Valgrind exec to test against"
23+
end
24+
end
25+
26+
def prep_project_yml_for_valgrind
27+
FileUtils.cp test_asset_path("project_as_gem.yml"), "project.yml"
28+
@c.uncomment_project_yml_option_for_test("- valgrind")
29+
end
30+
31+
def can_test_projects_with_valgrind_with_success
32+
@c.with_context do
33+
Dir.chdir @proj_name do
34+
prep_project_yml_for_valgrind
35+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
36+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
37+
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
38+
39+
output = `bundle exec ruby -S ceedling test:all 2>&1`
40+
expect($?.exitstatus).to match(0)
41+
42+
output = `bundle exec ruby -S ceedling valgrind:all 2>&1`
43+
expect($?.exitstatus).to match(0)
44+
expect(output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
45+
expect(output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
46+
end
47+
end
48+
end
49+
50+
def can_test_projects_with_valgrind_with_fail
51+
@c.with_context do
52+
Dir.chdir @proj_name do
53+
prep_project_yml_for_valgrind
54+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
55+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
56+
FileUtils.cp test_asset_path("test_example_file.c"), 'test/'
57+
58+
output = `bundle exec ruby -S ceedling test:all 2>&1`
59+
expect($?.exitstatus).to match(1)
60+
61+
output = `bundle exec ruby -S ceedling valgrind:all 2>&1`
62+
expect($?.exitstatus).to match(1)
63+
expect(output).to match(/==\d+== All heap blocks were freed -- no leaks are possible/)
64+
expect(output).to match(/==\d+== ERROR SUMMARY: 0 errors from 0 contexts/)
65+
end
66+
end
67+
end
68+
69+
def can_test_projects_with_valgrind_with_compile_error
70+
@c.with_context do
71+
Dir.chdir @proj_name do
72+
prep_project_yml_for_valgrind
73+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
74+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
75+
FileUtils.cp test_asset_path("test_example_file_boom.c"), 'test/'
76+
77+
output = `bundle exec ruby -S ceedling test:all 2>&1`
78+
expect($?.exitstatus).to match(1)
79+
80+
output = `bundle exec ruby -S ceedling valgrind:all 2>&1`
81+
expect($?.exitstatus).to match(1)
82+
expect(output).to match(/(?:ERROR: Ceedling Failed)|(?:Ceedling could not complete operations because of errors)/)
83+
end
84+
end
85+
end
86+
87+
def can_fetch_project_help_for_valgrind
88+
@c.with_context do
89+
Dir.chdir @proj_name do
90+
prep_project_yml_for_valgrind
91+
output = `bundle exec ruby -S ceedling help 2>&1`
92+
expect($?.exitstatus).to match(0)
93+
expect(output).to match(/ceedling valgrind:\*/i)
94+
expect(output).to match(/ceedling valgrind:all/i)
95+
end
96+
end
97+
end
98+
end

0 commit comments

Comments
 (0)