blob: 002414b427013fe3501c08464fd96e8069f8acb8 [file] [log] [blame]
#!/bin/bash
# Part of the Crubit project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
source gbash.sh || exit
source module gbash_unit.sh
readonly RS_BINDINGS_FROM_CC="${RUNFILES}/rs_bindings_from_cc/rs_bindings_from_cc"
readonly DEFAULT_CLANG_FORMAT_EXE_PATH="${RUNFILES}/google3/third_party/crosstool/google3_users/clang-format"
readonly DEFAULT_RUSTFMT_EXE_PATH="third_party/crosstool/rust/unstable/rustfmt_for_crubit_aspects"
function quote_escape() {
sed 's/"/\\"/g'
}
function test::cmd_line_api() {
EXPECT_FAIL "${RS_BINDINGS_FROM_CC}" "generator should return non-zero with no arguments"
EXPECT_SUCCEED \
"${RS_BINDINGS_FROM_CC} 2>&1 | grep 'please specify --target' > /dev/null" \
"generator should show help message for --target"
EXPECT_SUCCEED \
"${RS_BINDINGS_FROM_CC} --target=//:target 2>&1 | grep 'please specify --rs_out' > /dev/null" \
"generator should show help message for --rs_out"
EXPECT_SUCCEED \
"${RS_BINDINGS_FROM_CC} --target=//:target --rs_out=/foo 2>&1 | \
grep 'please specify --cc_out' > /dev/null" \
"generator should show help message for --cc_out"
local rs_out="${TEST_TMPDIR}/rs_api.rs"
local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" 2>&1 | \
grep 'please specify --crubit_support_path_format' > /dev/null" \
"generator should show help message for --crubit_support_path_format"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" 2>&1 \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" | \
grep 'please specify --clang_format_exe_path' > /dev/null" \
"generator should show help message for --clang_format_exe_path"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" 2>&1 \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" | \
grep 'please specify --rustfmt_exe_path' > /dev/null" \
"generator should show help message for --rustfmt_exe_path"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" 2>&1 \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" | \
grep 'please specify --public_headers' > /dev/null" \
"generator should show help message for --public_headers"
local hdr="${TEST_TMPDIR}/hello_world.h"
echo "int foo();" > "${hdr}"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${hdr}\" 2>&1 | \
grep 'please specify --target_args' > /dev/null" \
"generator should show help message for --target_args"
local json
json="$(cat <<-EOT
[{"t": "//foo/bar:baz", "h": ["${hdr}"], "f": ["experimental", "extern_c", "supported"]}]
EOT
)"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${hdr}\" \
--target_args=\"$(echo "${json}" | quote_escape)\""
EXPECT_FILE_NOT_EMPTY "${rs_out}"
EXPECT_FILE_NOT_EMPTY "${cc_out}"
}
function test::do_nothing() {
local rs_out="${TEST_TMPDIR}/rs_api.rs"
local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
local hdr="no_such_file.h"
local json
json="$(cat <<-EOT
[{"t": "//foo/bar:baz", "h": ["${hdr}"], "f": ["experimental", "extern_c", "supported"]}]
EOT
)"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${hdr}\" \
--target_args=\"$(echo "${json}" | quote_escape)\" \
--do_nothing"
EXPECT_FILE_NOT_EMPTY "${rs_out}"
EXPECT_FILE_NOT_EMPTY "${cc_out}"
EXPECT_SUCCEED "cat \"${rs_out}\" | grep '// intentionally left empty because --do_nothing was passed.'"
EXPECT_SUCCEED "cat \"${cc_out}\" | grep '// intentionally left empty because --do_nothing was passed.'"
}
function test::tool_returns_nonzero_on_invalid_input() {
local rs_out="${TEST_TMPDIR}/rs_api.rs"
local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
rm -rf "$rs_out"
rm -rf "$cc_out"
local hdr="${TEST_TMPDIR}/hello_world.h"
echo "int foo(); But this is not C++;" > "${hdr}"
local json
json="$(cat <<-EOT
[{"t": "//foo/bar:baz", "h": ["${hdr}"], "f": ["experimental", "extern_c", "supported"]}]
EOT
)"
EXPECT_FAIL \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//:target \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${hdr}\" \
--target_args=\"$(echo "${json}" | quote_escape)\" 2>&1"
# No output files should be created if the C++ input was invalid.
CHECK_FILE_NOT_EXISTS "${rs_out}"
CHECK_FILE_NOT_EXISTS "${cc_out}"
}
function test::public_headers() {
local rs_out="${TEST_TMPDIR}/rs_api.rs"
local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
local header_1="${TEST_TMPDIR}/header_1.h"
local header_2="${TEST_TMPDIR}/header_2.h"
echo "int function_1();" > "${header_1}"
echo "int function_2();" > "${header_2}"
local json
json="$(cat <<-EOT
[{"t": "//foo/bar:baz", "h": ["${header_1}", "${header_2}"], "f": ["experimental", "extern_c", "supported"]}]
EOT
)"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//foo/bar:baz \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${header_1},${header_2}\" \
--target_args=\"$(echo "${json}" | quote_escape)\" 2>&1"
EXPECT_SUCCEED "grep function_1 \"${rs_out}\"" "function_1 was not imported"
EXPECT_SUCCEED "grep function_2 \"${rs_out}\"" "function_2 was not imported"
}
function test::rustfmt_config_path() {
local rs_out="${TEST_TMPDIR}/rs_api.rs"
local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
local hdr="${TEST_TMPDIR}/hello_world.h"
echo "float MyFunction(float arg1, float arg2);" > "${hdr}"
local json
json="$(cat <<-EOT
[{"t": "//foo/bar:baz", "h": ["${hdr}"], "f": ["experimental", "extern_c", "supported"]}]
EOT
)"
#########################################################
# Testing the default `rustfmt` config.
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//foo/bar:baz \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${hdr}\" \
--target_args=\"$(echo "${json}" | quote_escape)\""
EXPECT_FILE_NOT_EMPTY "${rs_out}"
# Expecting:
# pub fn MyFunction(arg1: f32, arg2: f32) -> f32
EXPECT_SUCCEED \
"grep \"MyFunction.*arg1:.*f32,.*arg2:.*f32.*)\" \"${rs_out}\"" \
"Verify function args are on single line when using default rustfmt config (1)"
EXPECT_FAIL \
"grep \"^[^a-z]*arg1:[^a-z]*f32,[^a-z]*\\\$\" \"${rs_out}\"" \
"Verify function args are on single line when using default rustfmt config (2)"
#########################################################
# Testing a custom `rustfmt` config.
local rustfmt_config_path="${TEST_TMPDIR}/rustfmt.toml"
cat >"${rustfmt_config_path}" <<EOF
edition = "2021"
version = "Two"
fn_args_layout="Vertical"
EOF
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//foo/bar:baz\
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--rustfmt_config_path=\"${rustfmt_config_path}\" \
--public_headers=\"${hdr}\" \
--target_args=\"$(echo "${json}" | quote_escape)\""
EXPECT_FILE_NOT_EMPTY "${rs_out}"
# Expecting:
# pub fn MyFunction(
# arg1: f32,
# arg2: f32,
# ) -> f32
EXPECT_FAIL \
"grep \"MyFunction.*arg1:.*f32,.*arg2:.*f32.*)\" \"${rs_out}\"" \
"Verify function args are *not* on single line when using custom rustfmt config (1)"
EXPECT_SUCCEED \
"grep \"^[^a-z]*arg1:[^a-z]*f32,[^a-z]*\\\$\" \"${rs_out}\"" \
"Verify function args are *not* on single line when using custom rustfmt config (2)"
}
function test::crubit_support_path_format() {
local rs_out="${TEST_TMPDIR}/rs_api.rs"
local cc_out="${TEST_TMPDIR}/rs_api_impl.cc"
local hdr="${TEST_TMPDIR}/hello_world.h"
echo "int MyFunction();" > "${hdr}"
local json
json="$(cat <<-EOT
[{"t": "//foo/bar:baz", "h": ["${hdr}"], "f": ["experimental", "extern_c", "supported"]}]
EOT
)"
EXPECT_SUCCEED \
"\"${RS_BINDINGS_FROM_CC}\" \
--target=//foo/bar:baz \
--rs_out=\"${rs_out}\" \
--cc_out=\"${cc_out}\" \
--crubit_support_path_format=\"<test/specific/crubit/support/path/{header}>\" \
--clang_format_exe_path=\"${DEFAULT_CLANG_FORMAT_EXE_PATH}\" \
--rustfmt_exe_path=\"${DEFAULT_RUSTFMT_EXE_PATH}\" \
--public_headers=\"${hdr}\" \
--target_args=\"$(echo "${json}" | quote_escape)\""
EXPECT_FILE_NOT_EMPTY "${cc_out}"
EXPECT_SUCCEED \
"grep \"#include <test/specific/crubit/support/path/.*>\" \"${cc_out}\"" \
"Verify #include paths are based on the argument of --crubit_support_path_format"
}
gbash::unit::main "$@"