830 lines
21 KiB
Perl
830 lines
21 KiB
Perl
|
|
#! /usr/bin/perl
|
||
|
|
##
|
||
|
|
## Copyright 2019 The Android Open Source Project
|
||
|
|
##
|
||
|
|
## Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
## you may not use this file except in compliance with the License.
|
||
|
|
## You may obtain a copy of the License at
|
||
|
|
##
|
||
|
|
## http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
##
|
||
|
|
## Unless required by applicable law or agreed to in writing, software
|
||
|
|
## distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
## See the License for the specific language governing permissions and
|
||
|
|
## limitations under the License.
|
||
|
|
|
||
|
|
use File::Basename;
|
||
|
|
|
||
|
|
## mockcify version
|
||
|
|
##
|
||
|
|
## 0.6.0 Replace `extern` with `include` for mock_function_count_map
|
||
|
|
##
|
||
|
|
## 0.5.0 Add compilation check
|
||
|
|
##
|
||
|
|
## 0.4.0 Second re-write
|
||
|
|
##
|
||
|
|
## 0.3.2 Remove pragma from source file
|
||
|
|
##
|
||
|
|
## 0.3.1 Statically link return value to prevent 'this' pointer in function
|
||
|
|
##
|
||
|
|
## 0.3.0 Re-write parser.
|
||
|
|
##
|
||
|
|
## 0.2.1 Compilation units only include types and a single related header file
|
||
|
|
## Alphabetically sort functions by return value
|
||
|
|
## Add non-primative return data values in structure
|
||
|
|
##
|
||
|
|
## 0.2.0 First version
|
||
|
|
##
|
||
|
|
my $VERSION = "0.6.0";
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
use warnings;
|
||
|
|
|
||
|
|
my $YEAR = "2023";
|
||
|
|
my $TOKEN = "MOCKCIFY_TOKEN";
|
||
|
|
my $MOCKCIFY_BRACKET_GROUP = "MOCKCIFY_BRACKET_GROUP";
|
||
|
|
my $CLANG_FORMAT = "/usr/bin/clang-format-13";
|
||
|
|
my $CC = "g++";
|
||
|
|
my $LIBCHROME = "../../../../external/libchrome/";
|
||
|
|
my $COMPILE_SCREEN_ENABLED = 0;
|
||
|
|
|
||
|
|
my @structs;
|
||
|
|
|
||
|
|
my %function_signature;
|
||
|
|
my %function_return_types;
|
||
|
|
my @function_names;
|
||
|
|
my %function_params;
|
||
|
|
my %function_param_names;
|
||
|
|
my %function_param_types;
|
||
|
|
|
||
|
|
sub clang_format {
|
||
|
|
return `$CLANG_FORMAT --style="{ColumnLimit: 10000, PointerAlignment: Left, PointerBindsToType: true, FixNamespaceComments: true }"`;
|
||
|
|
}
|
||
|
|
|
||
|
|
## Create a temp directory for any cruft
|
||
|
|
my $TMPDIR="/tmp/mockcify";
|
||
|
|
system("mkdir -p $TMPDIR");
|
||
|
|
my $OUTDIR = "$TMPDIR/out/";
|
||
|
|
system("mkdir -p $OUTDIR");
|
||
|
|
my $INCDIR = "$TMPDIR/include/";
|
||
|
|
system("mkdir -p $INCDIR");
|
||
|
|
|
||
|
|
if (scalar(@ARGV == 0)) {
|
||
|
|
printf(STDERR "ERROR Must supply at least one argument\n");
|
||
|
|
exit 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $arg = shift @ARGV;
|
||
|
|
## Check only argument for debug vector
|
||
|
|
if ($arg =~ /--cla[ng]/) {
|
||
|
|
exit print clang_format();
|
||
|
|
} elsif ($arg =~ /--f[ilter]/) {
|
||
|
|
exit print read_stdin_and_filter_file();
|
||
|
|
} elsif ($arg =~ /--l[ines]/) {
|
||
|
|
exit print filter_lines(read_stdin_and_filter_file());
|
||
|
|
} elsif ($arg =~ /--i[nfo]/) {
|
||
|
|
my ($incs, $types, $funcs) = parse_info(filter_lines(read_stdin_and_filter_file()));
|
||
|
|
exit print @{$incs}, @{$types}, @{$funcs};
|
||
|
|
} elsif ($arg =~ /--co[mpile]/) {
|
||
|
|
exit compilation_screen("mock_" . shift @ARGV);
|
||
|
|
} elsif ($arg =~ /--cle[an]/) {
|
||
|
|
exit system("mv $TMPDIR $TMPDIR.deleted");
|
||
|
|
} elsif ($arg =~ /--u[nittest]/) {
|
||
|
|
print(STDERR "unit testing device");
|
||
|
|
}
|
||
|
|
|
||
|
|
sub help {
|
||
|
|
print <<EOF
|
||
|
|
Usage:
|
||
|
|
Specify a namespace on the command line for the shared structure data.
|
||
|
|
Then pipe the C file on stdin and one source and one header file will
|
||
|
|
be created based upon the namespace convention
|
||
|
|
|
||
|
|
mockcify.pl stack_l2cap_api < stack/l2cap/l2c_api.cc
|
||
|
|
|
||
|
|
Output files:
|
||
|
|
mock_stack_l2cap_api.cc
|
||
|
|
mock_stack_l2cap_api.h
|
||
|
|
|
||
|
|
The tool is not capable of parsing C++ and a workaround is to remove
|
||
|
|
C++ in the source prior to mock-C-fying the source.
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
## Only single arg is taken
|
||
|
|
my $namespace = $arg;
|
||
|
|
|
||
|
|
if ($namespace =~ /^--/) {
|
||
|
|
print(STDERR "ERROR Halting due to ill-formed namespace expression \'$namespace\'\n");
|
||
|
|
exit -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
###
|
||
|
|
### Phase 0: Prepare input and output file streams
|
||
|
|
###
|
||
|
|
|
||
|
|
## Default to stdout
|
||
|
|
my $FH_SRC;
|
||
|
|
my $FH_HDR;
|
||
|
|
|
||
|
|
my $src_filename;
|
||
|
|
my $hdr_filename;
|
||
|
|
## If namepace specified then write to that source and header
|
||
|
|
if ($namespace eq "TESTING") {
|
||
|
|
$FH_SRC = *STDOUT;
|
||
|
|
$FH_HDR = *STDOUT;
|
||
|
|
} else {
|
||
|
|
$src_filename="mock_" . $namespace . ".cc";
|
||
|
|
$hdr_filename="mock_" . $namespace . ".h";
|
||
|
|
|
||
|
|
open($FH_SRC, ">", $OUTDIR .$src_filename)
|
||
|
|
or die $!;
|
||
|
|
|
||
|
|
open($FH_HDR, ">", $OUTDIR .$hdr_filename)
|
||
|
|
or die $!;
|
||
|
|
}
|
||
|
|
|
||
|
|
###
|
||
|
|
### Phase 1: Read input file and apply single line filtering
|
||
|
|
###
|
||
|
|
my $text = read_stdin_and_filter_file();
|
||
|
|
|
||
|
|
###
|
||
|
|
### Phase 2: Apply Multiline filters
|
||
|
|
###
|
||
|
|
$text = filter_lines($text);
|
||
|
|
|
||
|
|
##
|
||
|
|
## Phase 3: Extract required mock information
|
||
|
|
##
|
||
|
|
my ($includes_ref, $typedefs_ref, $functions_ref, $usings_ref, $namespaces_ref) = parse_info($text);
|
||
|
|
my @includes = @{$includes_ref};
|
||
|
|
my @typedefs = @{$typedefs_ref};
|
||
|
|
my @functions = @{$functions_ref};
|
||
|
|
my @namespaces = @{$namespaces_ref};
|
||
|
|
my @usings = @{$usings_ref};
|
||
|
|
|
||
|
|
@includes = reject_include_list(@includes);
|
||
|
|
|
||
|
|
@functions = grep { parse_function_into_components ($_) } @functions;
|
||
|
|
|
||
|
|
##
|
||
|
|
## Phase 4: Output the mocks source and header
|
||
|
|
##
|
||
|
|
print_source($FH_SRC);
|
||
|
|
print_header($FH_HDR);
|
||
|
|
|
||
|
|
close ($FH_SRC);
|
||
|
|
close ($FH_HDR);
|
||
|
|
|
||
|
|
## Format the final source code files
|
||
|
|
if (defined $src_filename) {
|
||
|
|
system("clang-format", "-i", $OUTDIR . $src_filename);
|
||
|
|
system("clang-format", "-i", $OUTDIR . $hdr_filename);
|
||
|
|
}
|
||
|
|
|
||
|
|
print(STDERR "Generated files:", $OUTDIR . $src_filename, " ", $OUTDIR . $hdr_filename, "\n");
|
||
|
|
|
||
|
|
if ($COMPILE_SCREEN_ENABLED) {
|
||
|
|
my $rc = compilation_screen("mock_" . $namespace);
|
||
|
|
exit ($rc == 256) ?1 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub reject_include_list {
|
||
|
|
my @incs = ();
|
||
|
|
foreach (@_) {
|
||
|
|
next if (/init_flags/);
|
||
|
|
push(@incs, $_);
|
||
|
|
}
|
||
|
|
return @incs;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub compile_screen_failed {
|
||
|
|
my $src = shift @_;
|
||
|
|
print STDERR <<EOF
|
||
|
|
MOCK Compilation is EXPERIMENTAL ONLY
|
||
|
|
|
||
|
|
ERROR Failed to compile \'$src\' NOTE: This does not mean
|
||
|
|
the mock is unusable as the tool only screens the compilation.
|
||
|
|
|
||
|
|
There could be one of 3 problems:
|
||
|
|
1. Undeclared external surface or dependency
|
||
|
|
2. C++ code or namespaces mixed in with C code
|
||
|
|
3. An issue with proper mock'ing with mockcify.
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub compilation_screen {
|
||
|
|
my $base= shift @_;
|
||
|
|
my $src=$base . ".cc";
|
||
|
|
my $hdr=$base . ".h";
|
||
|
|
|
||
|
|
## Verious external or generated header not needed for mocks
|
||
|
|
foreach((
|
||
|
|
"test/mock/mock.h",
|
||
|
|
"src/init_flags.rs.h",
|
||
|
|
"src/message_loop_thread.rs.h",
|
||
|
|
"android/hardware/bluetooth/audio/2.2/IBluetoothAudioProvidersFactory.h",
|
||
|
|
"android/hardware/bluetooth/audio/2.2/types.h",
|
||
|
|
)) {
|
||
|
|
system("mkdir -p $INCDIR". dirname($_));
|
||
|
|
system("touch $INCDIR/$_");
|
||
|
|
}
|
||
|
|
my @incs = (
|
||
|
|
$INCDIR,
|
||
|
|
$LIBCHROME,
|
||
|
|
".",
|
||
|
|
"audio_hal_interface/",
|
||
|
|
"include/",
|
||
|
|
"stack/include/",
|
||
|
|
"btif/include/",
|
||
|
|
"internal_include",
|
||
|
|
"osi/include/",
|
||
|
|
"test/mock/",
|
||
|
|
"types/",
|
||
|
|
);
|
||
|
|
my @defs = (
|
||
|
|
"HAS_NO_BDROID_BUILDCFG",
|
||
|
|
);
|
||
|
|
|
||
|
|
my $link="test/mock/$hdr";
|
||
|
|
unlink "$INCDIR/$link";
|
||
|
|
symlink "$OUTDIR/$hdr", "$INCDIR/$link";
|
||
|
|
system("$CC -c -std=c++17 -o /dev/null -D" . join(" -D", @defs) . " -I" . join(" -I", @incs) . " $OUTDIR/$src");
|
||
|
|
my $rc = $?;
|
||
|
|
($? == 0)
|
||
|
|
? printf(STDERR "SUCCESS Compiled unit \'$src\'\n")
|
||
|
|
: compile_screen_failed($src);
|
||
|
|
return $rc;
|
||
|
|
}
|
||
|
|
|
||
|
|
###
|
||
|
|
### Phase 4.1: Print the source compilation unit and the associated structues
|
||
|
|
###
|
||
|
|
sub print_source {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print_copyright($FH);
|
||
|
|
print_generated_note($FH);
|
||
|
|
print_mock_decl_src($FH);
|
||
|
|
|
||
|
|
print_mock_header_include($FH);
|
||
|
|
print_usings($FH);
|
||
|
|
print_internal_structs($FH);
|
||
|
|
print_source_namespace_structs($FH);
|
||
|
|
print_static_return_values($FH);
|
||
|
|
print_mocked_functions($FH);
|
||
|
|
|
||
|
|
print $FH "// END mockcify generation\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
###
|
||
|
|
### Phase 4.2 Print the header unit to be included with the test
|
||
|
|
###
|
||
|
|
sub print_header {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print_copyright($FH);
|
||
|
|
print_pragma($FH);
|
||
|
|
print_generated_note($FH);
|
||
|
|
print_mock_decl_hdr($FH);
|
||
|
|
|
||
|
|
print_includes($FH);
|
||
|
|
print_usings($FH);
|
||
|
|
print_defs($FH);
|
||
|
|
print_header_test_mock_namespace_structs($FH);
|
||
|
|
print $FH "// END mockcify generation";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_function_param_names {
|
||
|
|
my $name = shift @_;
|
||
|
|
my @param_names;
|
||
|
|
foreach (0..$#{$function_param_names{$name}}) {
|
||
|
|
my $param_name = $function_param_names{$name}[$_];
|
||
|
|
my $param_type = $function_param_types{$name}[$_];
|
||
|
|
|
||
|
|
if ($param_type =~ /unique_ptr/) {
|
||
|
|
## Wrap name in a move operation
|
||
|
|
push(@param_names, "std::move($param_name)");
|
||
|
|
} else {
|
||
|
|
push(@param_names, $param_name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return join(',', @param_names);
|
||
|
|
}
|
||
|
|
|
||
|
|
##
|
||
|
|
## Parse a function signature into 4 basic components and insert into
|
||
|
|
## the global hashes and arrays.
|
||
|
|
## 1. @function return type
|
||
|
|
## 2. @function name
|
||
|
|
## 3. %param types
|
||
|
|
## 4. %param names
|
||
|
|
##
|
||
|
|
sub parse_function_into_components {
|
||
|
|
my $function = shift @_;
|
||
|
|
## Ensure this is really a function string
|
||
|
|
assert(substr $function, -1 eq ')');
|
||
|
|
|
||
|
|
## Split on first occurrence of open paren to get return
|
||
|
|
## type and name of function
|
||
|
|
my ($return_type_and_name, $params) = split '\(', $function, 2;
|
||
|
|
if (!defined($params)) {
|
||
|
|
printf(STDERR "WARNING \'params\' is undefined \"$params\" function:\'$function\'\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
## Remove input params closing paren
|
||
|
|
$params=~ s/\).*$//;
|
||
|
|
|
||
|
|
## Parse the return type and function name
|
||
|
|
my ($return_type, $name) = $return_type_and_name =~ /(.*)\s(.*)/;
|
||
|
|
|
||
|
|
if (!defined($name)) {
|
||
|
|
printf(STDERR "WARNING \'name\' is undefined \"$return_type_and_name\" a [con|des]tructor ?\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if ($name =~ /::/) {
|
||
|
|
printf(STDERR "WARNING \'name\' is unhandled class method \'$name\'\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
## Store away complete function signature
|
||
|
|
$function_signature{$name} = $function;
|
||
|
|
|
||
|
|
## Store away the parameter type and names
|
||
|
|
chomp($params);
|
||
|
|
$function_params{$name} = $params;
|
||
|
|
|
||
|
|
## Parse the parameter types and names
|
||
|
|
my @param_types;
|
||
|
|
my @param_names;
|
||
|
|
|
||
|
|
## Skip when void keyword used for no parameters
|
||
|
|
if ($params ne "void") {
|
||
|
|
foreach (split ',', $params) {
|
||
|
|
s/^\s+//;
|
||
|
|
if (/\(/) {
|
||
|
|
## TODO Parameter is a C style function
|
||
|
|
my @vars;
|
||
|
|
my @f = split /[\(\)]/;
|
||
|
|
push(@vars, substr $f[1], 1);
|
||
|
|
} else {
|
||
|
|
## Store the type and name
|
||
|
|
my ($type, $name) = /(.*)\s(.*)/;
|
||
|
|
push(@param_names, $name);
|
||
|
|
push(@param_types, $type);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
push(@function_names, $name);
|
||
|
|
$function_return_types{$name} = $return_type;
|
||
|
|
$function_param_types{$name} = \@param_types;
|
||
|
|
$function_param_names{$name} = \@param_names;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
##
|
||
|
|
## Read a file from stdin and does a first pass simple
|
||
|
|
## filtering that removes single lines.
|
||
|
|
##
|
||
|
|
sub read_stdin_and_filter_file {
|
||
|
|
my @filtered_lines;
|
||
|
|
my @clang_format=clang_format();
|
||
|
|
foreach (@clang_format) {
|
||
|
|
## Update header guards with compiler #pragma for proper
|
||
|
|
## decision processing of header or source
|
||
|
|
s/^#ifndef [A-Z_0-9]+_H/#pragma once/;
|
||
|
|
|
||
|
|
unless (/^extern/
|
||
|
|
or /^#define /
|
||
|
|
or / = \{/
|
||
|
|
or /^#if /
|
||
|
|
or /^constexpr/
|
||
|
|
or /^#ifdef/
|
||
|
|
or /^#ifndef/
|
||
|
|
or /^#else/
|
||
|
|
or /^enum/
|
||
|
|
or /^static.*;$/
|
||
|
|
or /^#endif/) {
|
||
|
|
## Remove any single line C style comments
|
||
|
|
s:/\*.*\*/::;
|
||
|
|
push(@filtered_lines, $_);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return join('', @filtered_lines);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub filter_lines {
|
||
|
|
$_ = shift @_;
|
||
|
|
## Remove anonymous namespaces
|
||
|
|
## $text =~ s/namespace \{.*\n\} \/\/ namespace/\n/sg;
|
||
|
|
s/namespace \{.*\n\} \/\/ namespace?/\n/sg;
|
||
|
|
s/namespace \{.?\n\}/\n/g;
|
||
|
|
## Remove C style comments
|
||
|
|
s/\s*\/\*(?:(?!\*\/).)*\*\/\n?/\n/sg;
|
||
|
|
## Remove Cpp style comments
|
||
|
|
s/\s*\/\/.*//g;
|
||
|
|
## Remove unnecessary bluetooth osi specific modifier
|
||
|
|
s/UNUSED_ATTR//g;
|
||
|
|
## Modify internally defined structure typedefs
|
||
|
|
s/typedef struct \{.*?\n\} (\w+);/typedef struct $MOCKCIFY_BRACKET_GROUP $1;/sg;
|
||
|
|
## Modify internally defined structure typedefs
|
||
|
|
s/typedef struct (\w+) \{.*?\n\} (\w+);/struct $1 $MOCKCIFY_BRACKET_GROUP;/sg;
|
||
|
|
## Modify internally defined structures
|
||
|
|
s/struct (\w+) \{.*?\n\};/struct $1 $MOCKCIFY_BRACKET_GROUP;/sg;
|
||
|
|
## Remove lines only with spaces
|
||
|
|
s/^\s+$//sg;
|
||
|
|
return $_;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub parse_info {
|
||
|
|
if (/\n#pragma once\n/) {
|
||
|
|
return parse_info_header(shift @_);
|
||
|
|
} else {
|
||
|
|
return parse_info_source(shift @_);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub parse_info_header {
|
||
|
|
my (@includes, @typedefs, @functions, @usings, @namespaces);
|
||
|
|
foreach (split('\n')) {
|
||
|
|
chomp();
|
||
|
|
if (/^ /) {
|
||
|
|
} elsif (/^#include /) {
|
||
|
|
push(@includes, $_);
|
||
|
|
} elsif (/^typedef /) {
|
||
|
|
push @typedefs, $_;
|
||
|
|
} elsif ($_ =~ /^ *$/) {
|
||
|
|
# Skip function body indicated by indentation
|
||
|
|
} elsif ($_ =~ /^}/) {
|
||
|
|
# Skip function curly bracket closure
|
||
|
|
} elsif (/^namespace/) {
|
||
|
|
push @namespaces, $_;
|
||
|
|
} elsif (/\(/) {
|
||
|
|
# Add function signature
|
||
|
|
chomp();
|
||
|
|
## Remove all function body after signature
|
||
|
|
s/{.*$//;
|
||
|
|
## Remove whitespace on both ends
|
||
|
|
s/^\s+|\s+$//g;
|
||
|
|
## Ignore locally linked functions
|
||
|
|
next if (/^static/);
|
||
|
|
## Reduce all remaining whitespace to a single space
|
||
|
|
s/\s+/ /g;
|
||
|
|
## Remove any semi colons
|
||
|
|
s/;//g;
|
||
|
|
push(@functions, "$_\n");
|
||
|
|
} else {
|
||
|
|
# Not a function. skip
|
||
|
|
}
|
||
|
|
}
|
||
|
|
printf(STDERR "Parsed HEADER lines includes:%d typedefs:%d functions:%d\n",
|
||
|
|
scalar(@includes), scalar(@typedefs), scalar(@functions));
|
||
|
|
return (\@includes, \@typedefs, \@functions, \@usings, \@namespaces);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub parse_info_source{
|
||
|
|
my @s = split('\n', $_);
|
||
|
|
my (@includes, @typedefs, @functions, @usings, @namespaces);
|
||
|
|
foreach (@s) {
|
||
|
|
chomp();
|
||
|
|
if (/^ /) {
|
||
|
|
} elsif (/^#include /) {
|
||
|
|
push @includes, $_;
|
||
|
|
} elsif (/^typedef /) {
|
||
|
|
push @typedefs, $_;
|
||
|
|
} elsif (/^using /) {
|
||
|
|
push @usings, $_;
|
||
|
|
} elsif (/^namespace/) {
|
||
|
|
push @namespaces, $_;
|
||
|
|
} elsif ($_ =~ /^ *$/) {
|
||
|
|
# Skip function body indicated by indentation
|
||
|
|
} elsif ($_ =~ /^}/) {
|
||
|
|
# Skip function curly bracket closure
|
||
|
|
} elsif (/\{/) {
|
||
|
|
# Add function signature
|
||
|
|
chomp();
|
||
|
|
## Remove all function body after signature
|
||
|
|
s/{.*$//;
|
||
|
|
## Remove whitespace on both ends
|
||
|
|
s/^\s+|\s+$//g;
|
||
|
|
## Ignore locally linked functions
|
||
|
|
next if (/^static/);
|
||
|
|
## Reduce all remaining whitespace to a single space
|
||
|
|
s/\s+/ /g;
|
||
|
|
push(@functions, "$_\n");
|
||
|
|
} else {
|
||
|
|
# Not a function. skip
|
||
|
|
}
|
||
|
|
}
|
||
|
|
printf(STDERR "Parsed SOURCE lines includes:%d typedefs:%d functions:%d\n",
|
||
|
|
scalar(@includes), scalar(@typedefs), scalar(@functions));
|
||
|
|
return (\@includes, \@typedefs, \@functions, \@usings, \@namespaces);
|
||
|
|
}
|
||
|
|
|
||
|
|
## Returns the default type specified by the function return type.
|
||
|
|
## These are processed in priority order.
|
||
|
|
sub get_default_return_value_from_type {
|
||
|
|
$_ = shift @_;
|
||
|
|
assert($_ ne '');
|
||
|
|
if (/^bool/) {
|
||
|
|
return "false";
|
||
|
|
} elsif (/\*$/ or /^std::unique_ptr/ or /^std::shared_ptr/) { ## Pointer return val
|
||
|
|
return "nullptr";
|
||
|
|
} elsif (/^void/) {
|
||
|
|
return "";
|
||
|
|
} elsif (/^std::string/) {
|
||
|
|
return "std::string()";
|
||
|
|
} elsif (/^std::list\<entry_t\>::iterator/) {
|
||
|
|
return "static std::list<entry_t> v";
|
||
|
|
} elsif (/^std::list\<section_t\>::iterator/) {
|
||
|
|
return "std::list<section_t>";
|
||
|
|
} elsif (/reactor_status_t/) {
|
||
|
|
return "REACTOR_STATUS_DONE";
|
||
|
|
} elsif (/tL2CAP_LE_RESULT_CODE/) {
|
||
|
|
return "L2CAP_LE_RESULT_CONN_OK";
|
||
|
|
} elsif (/std::vector/) {
|
||
|
|
return "retval";
|
||
|
|
} elsif (/tBT_TRANSPORT/) {
|
||
|
|
return "BT_TRANSPORT_BR_EDR";
|
||
|
|
} elsif (/tSDP_STATUS/) {
|
||
|
|
return "SDP_SUCCESS";
|
||
|
|
} elsif (/tGATT_STATUS/) {
|
||
|
|
return "GATT_SUCCESS";
|
||
|
|
} elsif (/tHID_STATUS/) {
|
||
|
|
return "HID_SUCCESS";
|
||
|
|
} elsif (/future_t\*/) {
|
||
|
|
return "FUTURE_FAIL";
|
||
|
|
} elsif(/bt_status_t/) {
|
||
|
|
return "BT_STATUS_SUCCESS";
|
||
|
|
} elsif(/.*module_t\*/) {
|
||
|
|
return "nullptr";
|
||
|
|
} elsif(/btav_a2dp_codec_index_t/) {
|
||
|
|
return "BTAV_A2DP_CODEC_INDEX_SOURCE_MIN";
|
||
|
|
} else {
|
||
|
|
## Decay to int type
|
||
|
|
return "0";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
##
|
||
|
|
## Various print output boilerplate
|
||
|
|
###
|
||
|
|
sub print_copyright {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF
|
||
|
|
/*
|
||
|
|
* Copyright $YEAR The Android Open Source Project
|
||
|
|
*
|
||
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
* you may not use this file except in compliance with the License.
|
||
|
|
* You may obtain a copy of the License at
|
||
|
|
*
|
||
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
* See the License for the specific language governing permissions and
|
||
|
|
* limitations under the License.
|
||
|
|
*/
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
## Print body of each function
|
||
|
|
sub print_mocked_functions {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Mocked functions, if any
|
||
|
|
EOF
|
||
|
|
foreach my $name (sort @function_names) {
|
||
|
|
my $return_type = $function_return_types{$name};
|
||
|
|
assert($return_type ne '');
|
||
|
|
|
||
|
|
my $return_keyword = $return_type eq "void" ? "" : "return";
|
||
|
|
my $function_param_names = get_function_param_names($name);
|
||
|
|
|
||
|
|
print $FH <<EOF;
|
||
|
|
$function_signature{$name} {
|
||
|
|
inc_func_call_count(__func__);
|
||
|
|
${return_keyword} test::mock::${namespace}::${name}($function_param_names);
|
||
|
|
}
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Mocked functions complete
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_static_return_values {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Mocked function return values, if any
|
||
|
|
namespace test {
|
||
|
|
namespace mock {
|
||
|
|
namespace $namespace {
|
||
|
|
|
||
|
|
EOF
|
||
|
|
foreach my $name (sort @function_names) {
|
||
|
|
$name =~ s/\s+$//;
|
||
|
|
my $return_type = $function_return_types{$name};
|
||
|
|
assert($return_type ne '');
|
||
|
|
|
||
|
|
next if ($return_type eq "void");
|
||
|
|
my $default_return_value = get_default_return_value_from_type($return_type);
|
||
|
|
print $FH "${return_type} ${name}::return_value = ${default_return_value};\n";
|
||
|
|
}
|
||
|
|
print $FH <<EOF;
|
||
|
|
|
||
|
|
} // namespace $namespace
|
||
|
|
} // namespace mock
|
||
|
|
} // namespace test
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
##
|
||
|
|
## Collection of mocked functions
|
||
|
|
sub print_source_namespace_structs {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
namespace test {
|
||
|
|
namespace mock {
|
||
|
|
namespace $namespace {
|
||
|
|
|
||
|
|
// Function state capture and return values, if needed
|
||
|
|
EOF
|
||
|
|
foreach my $name (sort @function_names) {
|
||
|
|
print $FH "struct $name $name;\n";
|
||
|
|
}
|
||
|
|
print $FH <<EOF;
|
||
|
|
|
||
|
|
} // namespace $namespace
|
||
|
|
} // namespace mock
|
||
|
|
} // namespace test
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
##
|
||
|
|
## Print the definitions of the various structures for the header files
|
||
|
|
##
|
||
|
|
sub print_header_test_mock_namespace_structs {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
namespace test {
|
||
|
|
namespace mock {
|
||
|
|
namespace $namespace {
|
||
|
|
|
||
|
|
// Shared state between mocked functions and tests
|
||
|
|
EOF
|
||
|
|
foreach my $name (sort @function_names) {
|
||
|
|
my $input_params = $function_params{$name};
|
||
|
|
my $return_type = $function_return_types{$name};
|
||
|
|
my @param_names = $function_param_names{$name};
|
||
|
|
assert($return_type ne '');
|
||
|
|
|
||
|
|
my $function_param_names = get_function_param_names($name);
|
||
|
|
my $return_keyword = $return_type eq "void" ? "" : "return";
|
||
|
|
my $return_statement = $return_type eq "void" ? "" : "return return_value;";
|
||
|
|
my $return_definition = $return_type eq "void" ? "" : "static $return_type return_value;";
|
||
|
|
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Name: $name
|
||
|
|
// Params: $input_params
|
||
|
|
// Return: $return_type
|
||
|
|
struct $name {
|
||
|
|
EOF
|
||
|
|
if ($return_definition ne "") {
|
||
|
|
print $FH "$return_definition\n";
|
||
|
|
}
|
||
|
|
print $FH <<EOF;
|
||
|
|
std::function<$return_type($input_params)> body{[]($input_params){$return_statement}};
|
||
|
|
$return_type operator()($input_params) { ${return_keyword} body($function_param_names);};
|
||
|
|
};
|
||
|
|
extern struct $name $name;
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
print $FH <<EOF;
|
||
|
|
} // namespace $namespace
|
||
|
|
} // namespace mock
|
||
|
|
} // namespace test
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_pragma {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF
|
||
|
|
#pragma once
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_generated_note {
|
||
|
|
my $FH = shift @_;
|
||
|
|
my $gen = scalar(@functions);
|
||
|
|
print $FH <<EOF;
|
||
|
|
/*
|
||
|
|
* Generated mock file from original source file
|
||
|
|
* Functions generated:$gen
|
||
|
|
*
|
||
|
|
* mockcify.pl ver $VERSION
|
||
|
|
*/
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_usings {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Original usings
|
||
|
|
EOF
|
||
|
|
foreach (sort @usings) {
|
||
|
|
print $FH $_, "\n";
|
||
|
|
}
|
||
|
|
print($FH "\n");;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_includes {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Original included files, if any
|
||
|
|
// NOTE: Since this is a mock file with mock definitions some number of
|
||
|
|
// include files may not be required. The include-what-you-use
|
||
|
|
// still applies, but crafting proper inclusion is out of scope
|
||
|
|
// for this effort. This compilation unit may compile as-is, or
|
||
|
|
// may need attention to prune from (or add to ) the inclusion set.
|
||
|
|
EOF
|
||
|
|
foreach (sort @includes) {
|
||
|
|
print $FH $_, "\n";
|
||
|
|
}
|
||
|
|
print($FH "\n");;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_mock_header_include {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Mock include file to share data between tests and mock
|
||
|
|
#include "test/mock/mock_${namespace}.h"
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_mock_decl_hdr {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
#include <cstdint>
|
||
|
|
#include <functional>
|
||
|
|
#include <map>
|
||
|
|
#include <string>
|
||
|
|
|
||
|
|
#include "test/common/mock_functions.h"
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_mock_decl_src {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
#include <cstdint>
|
||
|
|
#include <functional>
|
||
|
|
#include <map>
|
||
|
|
#include <string>
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_defs {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Mocked compile conditionals, if any
|
||
|
|
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
sub print_internal_structs {
|
||
|
|
my $FH = shift @_;
|
||
|
|
print $FH <<EOF;
|
||
|
|
// Mocked internal structures, if any
|
||
|
|
EOF
|
||
|
|
|
||
|
|
foreach (sort @structs) {
|
||
|
|
print $FH $_,"\n"};
|
||
|
|
print $FH "\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub assert {
|
||
|
|
my ($condition, $msg) = @_;
|
||
|
|
return if $condition;
|
||
|
|
if (!$msg) {
|
||
|
|
my ($pkg, $file, $line) = caller(0);
|
||
|
|
open my $fh, "<", $file;
|
||
|
|
my @lines = <$fh>;
|
||
|
|
close $fh;
|
||
|
|
$msg = "$file:$line: " . $lines[$line - 1];
|
||
|
|
}
|
||
|
|
die "Assertion failed: $msg";
|
||
|
|
}
|