Skip to content

Commit

Permalink
Merge pull request #190 from RQC-HU/allow-testing-outside-bindir
Browse files Browse the repository at this point in the history
Allow testing outside bindir
  • Loading branch information
kohei-noda-qcrg authored Feb 8, 2025
2 parents 243b747 + 1229220 commit 03b5f9e
Show file tree
Hide file tree
Showing 21 changed files with 93 additions and 75 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
# Ignore object files
bin/

# Ignore build directory
build/
# Ignore build* directory
build*

# Ignore object files and module files
*.o
Expand Down
13 changes: 6 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ option(MPI "Enable MPI?" OFF)
option(OPENMP "Enable OpenMP?" OFF)
option(MKL "Enable MKL?" ON)

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin) # Set executable output path
set(CMAKE_Fortran_MODULE_DIRECTORY ${PROJECT_BINARY_DIR}/modules) # Set module output path

# Deny In-source build ( Ref : https://github.com/eigenteam/eigen-git-mirror/blob/36b95962756c1fce8e29b1f8bc45967f30773c00/CMakeLists.txt#L7-L9)
Expand Down Expand Up @@ -73,10 +72,10 @@ endif()

add_subdirectory(src)
add_subdirectory(test)
configure_file(${PROJECT_SOURCE_DIR}/tools/dcaspt2_input ${EXECUTABLE_OUTPUT_PATH}/dcaspt2 COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/tools/gen_dcaspt2_restart ${EXECUTABLE_OUTPUT_PATH}/gen_dcaspt2_restart COPYONLY)
install(PROGRAMS ${EXECUTABLE_OUTPUT_PATH}/dcaspt2 DESTINATION ${CMAKE_INSTALL_PREFIX})
install(PROGRAMS ${EXECUTABLE_OUTPUT_PATH}/gen_dcaspt2_restart DESTINATION ${CMAKE_INSTALL_PREFIX})
configure_file(${PROJECT_SOURCE_DIR}/tools/dcaspt2_input ${PROJECT_BINARY_DIR}/dcaspt2 COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/tools/gen_dcaspt2_restart ${PROJECT_BINARY_DIR}/gen_dcaspt2_restart COPYONLY)
install(PROGRAMS ${PROJECT_BINARY_DIR}/dcaspt2 DESTINATION ${CMAKE_INSTALL_PREFIX})
install(PROGRAMS ${PROJECT_BINARY_DIR}/gen_dcaspt2_restart DESTINATION ${CMAKE_INSTALL_PREFIX})

# Create a custom target named commit_hash
add_custom_target(commit_hash ALL)
Expand All @@ -87,9 +86,9 @@ add_custom_command(
PRE_BUILD
COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DEXECUTABLE_OUTPUT_PATH=${EXECUTABLE_OUTPUT_PATH}
-DPROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}
-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}
-P ${PROJECT_SOURCE_DIR}/cmake/commit_hash.cmake
ALWAYS
)
install(FILES ${EXECUTABLE_OUTPUT_PATH}/.commit_hash DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES ${PROJECT_BINARY_DIR}/.commit_hash DESTINATION ${CMAKE_INSTALL_PREFIX})
4 changes: 2 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ git clone --depth=1 https://github.com/RQC-HU/dirac_caspt2.git
- このプログラムはsetupスクリプトを使用してビルドできます
- CMakeコマンドを直接使用してビルドすることもできますが、setupスクリプトを使用することをおすすめします
- CMakeを直接使用してビルドしたい場合は、[CMakeビルドオプション](#cmakeビルドオプション)を参照してください
- デフォルトでは、ビルドした結果のバイナリとdcaspt2スクリプトはbinディレクトリ直下に配置されます
- デフォルトでは、ビルドした結果のバイナリとdcaspt2スクリプトはbuildディレクトリ直下に配置されます

### Basic build

Expand Down Expand Up @@ -247,7 +247,7 @@ pytest --all

### Calculation

- ビルド後に作られるbinディレクトリ直下またはprefixを指定した場合はインストール先のディレクトリ直下のdcaspt2スクリプトを用いて計算を行います
- ビルド後に作られるbuildディレクトリ直下またはprefixを指定した場合はインストール先のディレクトリ直下のdcaspt2スクリプトを用いて計算を行います
- dcaspt2スクリプトで使用可能なオプションはdcaspt2 -hで確認できます
- 例えば以下のように使用します

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,11 @@ The following custom CMake build options are currently supported
### Calculation

- You can use this program with the dcaspt2 script
- The dcaspt2 script is located in the bin directory or in the destination directory if installed by specifying with a --prefix option.
- The dcaspt2 script is located in the build directory (by default) or in the destination directory if installed by specifying with a --prefix option.

```sh
# If you did not install with --prefix
/path/to/dirac_caspt2/bin/dcaspt2 -i input_file
/path/to/dirac_caspt2/build/dcaspt2 -i input_file
# If you installed with --prefix
$PREFIX/dcaspt2 -i input_file
```
Expand Down
4 changes: 2 additions & 2 deletions cmake/commit_hash.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
message(STATUS "Getting git hash")
message(STATUS "commit_hash.cmake PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message(STATUS "commit_hash.cmake EXECUTABLE_OUTPUT_PATH: ${EXECUTABLE_OUTPUT_PATH}")
message(STATUS "commit_hash.cmake PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message(STATUS "commit_hash.cmake CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
set(git_hash "unknown") # Default value

Expand All @@ -17,4 +17,4 @@ else()
set(git_hash "unknown")
endif()

file(WRITE ${EXECUTABLE_OUTPUT_PATH}/.commit_hash "${git_hash}")
file(WRITE ${PROJECT_BINARY_DIR}/.commit_hash "${git_hash}")
21 changes: 15 additions & 6 deletions test/conftest.py → conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import List, Tuple

import pytest

from module_testing import create_test_command_dcaspt2

slow_only_option = "--slowonly"
Expand All @@ -16,6 +17,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(slow_only_option, action="store_true", default=False, help="run only very slow tests")
parser.addoption(dev_option, action="store_true", default=False, help="run tests for development")
parser.addoption(runall_option, action="store_true", default=False, help="run all tests")
parser.addoption("-B", "--build-dir", action="store", default="build", help="build directory [default: build]")
parser.addoption(
"--mpi",
type=int,
Expand Down Expand Up @@ -48,7 +50,6 @@ def save(request: pytest.FixtureRequest):

@pytest.fixture(scope="function")
def env_setup_caspt2(request: pytest.FixtureRequest, mpi_num_process: int, omp_num_threads: int, save: bool) -> Tuple[Path, Path, Path, Path, str]:
root_path = Path(__file__).parent.parent
test_path = Path(request.fspath).parent
# test_name is the name of the test file without the extension and the first test_.
# (e.g.) /path/to/test/slow/c1_methane_slow/test_c1_methane_slow.py -> c1_methane_slow
Expand All @@ -63,7 +64,7 @@ def env_setup_caspt2(request: pytest.FixtureRequest, mpi_num_process: int, omp_n
ref_output_path = test_path / ref_output_file
output_path = test_path / output_filename
latest_passed_path = test_path / latest_passed_output
dcaspt2 = root_path / "bin/dcaspt2"
dcaspt2 = request.config.build_dir / "dcaspt2"
test_command = create_test_command_dcaspt2(dcaspt2, mpi_num_process, omp_num_threads, input_path, output_path, test_path, save)

return (
Expand All @@ -77,7 +78,6 @@ def env_setup_caspt2(request: pytest.FixtureRequest, mpi_num_process: int, omp_n

@pytest.fixture(scope="function")
def env_setup_ivo(request: pytest.FixtureRequest, mpi_num_process: int, omp_num_threads: int, save: bool) -> Tuple[Path, Path, Path, Path, str]:
root_path = Path(__file__).parent.parent
test_path = Path(request.fspath).parent
# test_name is the name of the test file without the extension and the first test_.
# (e.g.) /path/to/test/dev/ivo_c32h_n2_dev_dirac22/test_ivo_c32h_n2_dev_dirac22.py -> ivo_c32h_n2_dev_dirac22
Expand All @@ -95,7 +95,7 @@ def env_setup_ivo(request: pytest.FixtureRequest, mpi_num_process: int, omp_num_
latest_passed_DFPCMONEW_path = test_path / latest_passed_output
output_path = test_path / output_filename
latest_passed_output_path = test_path / latest_passed_output
dcaspt2 = root_path / "bin/dcaspt2"
dcaspt2 = request.config.build_dir / "dcaspt2"
is_ivo = True
test_command = create_test_command_dcaspt2(dcaspt2, mpi_num_process, omp_num_threads, input_path, output_path, test_path, save, is_ivo)

Expand All @@ -115,15 +115,24 @@ def env_setup_gen_restart_file(request: pytest.FixtureRequest) -> Tuple[Path, Pa
caller_name = request.function.__name__
caller_path = Path(request.node.path).expanduser().resolve().parent

root_path = Path(__file__).parent.parent
gen_restart_path = root_path / "bin/gen_dcaspt2_restart"
gen_restart_path = request.config.build_dir / "gen_dcaspt2_restart"
test_path = Path(request.node.path).parent
input_path = caller_path / f"{caller_name}.in"
expected_path = caller_path / f"expected_{caller_name}"
return (gen_restart_path, test_path, input_path, expected_path)

@pytest.fixture(scope="function")
def env_setup_unittest(request: pytest.FixtureRequest):
def _env_setup_unittest(exe_name: str) -> Path:
build_dir = request.config.build_dir
exe_path = build_dir / exe_name
return exe_path
return _env_setup_unittest

def pytest_configure(config: pytest.Config) -> None:
config.build_dir = Path(config.getoption("--build-dir")).expanduser().resolve()
if not config.build_dir.exists():
raise FileNotFoundError(f"build directory {config.build_dir} does not exist.")
config.addinivalue_line("markers", "slowonly: mark test as slow to run")
config.addinivalue_line("markers", "dev: mark test as for development")

Expand Down
2 changes: 2 additions & 0 deletions test/module_testing.py → module_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def check_test_returncode(process: "subprocess.CompletedProcess[str]") -> None:
if process.returncode != 0:
raise Exception("ERROR: Process failed. return code status : " + str(process.returncode))


def get_multi_caspt2_energy_from_output_file(file_path: str) -> List[float]:
with open(file_path, encoding="utf-8", mode="r") as output_file:
try:
Expand All @@ -91,6 +92,7 @@ def get_multi_caspt2_energy_from_output_file(file_path: str) -> List[float]:
error_message = f"{error}\nERROR: Failed to get the CASPT2 energy from the reference file {file_path}."
raise Exception(error_message)


def get_caspt2_energy_from_output_file(file_path: str) -> float:
with open(file_path, encoding="utf-8", mode="r") as output_file:
try:
Expand Down
51 changes: 30 additions & 21 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ from pathlib import Path

def define_args() -> "argparse.Namespace":
parser = argparse.ArgumentParser(description="This script is used as a setup script for this project.")
parser.add_argument("-B", "--build-dir", type=str, dest="build_dir", default="build", help="Specify the build directory. [default: build]")
parser.add_argument("--build", action="store_true", help="Build the project after the setup. [default: False]")
parser.add_argument("--build-type", type=str, help="Specify the build type. (release, debug can be specified.) [default: release]")
parser.add_argument("--fc", type=str, help="The path to the Fortran compiler.(If not specified, the default compiler will be used.)")
Expand All @@ -32,7 +33,16 @@ def define_args() -> "argparse.Namespace":
return parser.parse_args()


def preprocess(args: "argparse.Namespace", exec_path: Path, script_path: Path) -> str:
def validate_build_dir(build_dir: Path, script_path: Path):
banned_dir_names = ["__pycache__", ".devcontainer", ".git", ".github", ".pytest_cache", ".vscode", "cmake", "src", "test", "tools"]
banned_dirs = set(script_path / banned_dir_name for banned_dir_name in banned_dir_names)
if build_dir in banned_dirs:
logging.error(f"Build directory {build_dir} is not allowed because it is a reserved directory.")
logging.error(f"banned_dirs: {[d.as_posix() for d in banned_dirs]}")
raise ValueError(f"Build directory {build_dir} is not allowed.")


def preprocess(args: "argparse.Namespace", build_dir: Path) -> str:
def check_cmake_version() -> None:
logging.info("Checking if CMake is installed and the version is 3.14 or later...")
try:
Expand Down Expand Up @@ -69,15 +79,12 @@ def preprocess(args: "argparse.Namespace", exec_path: Path, script_path: Path) -
raise # raise the FileNotFoundError exception to exit the script with an error code.

def clean_dir() -> None:
"""Remove build and bin directories before configuring the source code."""
"""Remove build directories before configuring the source code."""
logging.info("Cleaning the build directory...")
remove_dirs_name = ["build", "bin"]
for rmdir_name in remove_dirs_name:
rmdir = (script_path / rmdir_name).resolve()
logging.info(f"Checking if {rmdir} exists...")
if rmdir.exists():
shutil.rmtree(rmdir)
logging.info(f"Removed the directory {rmdir}")
logging.info(f"Checking if {build_dir} exists...")
if build_dir.exists():
shutil.rmtree(build_dir)
logging.info(f"Removed the directory {build_dir}")

def set_compiler() -> str:
if args.fc is not None:
Expand Down Expand Up @@ -135,51 +142,53 @@ def preprocess(args: "argparse.Namespace", exec_path: Path, script_path: Path) -
return cmake_flags


def configure_source(cmake_flags: str, script_path: Path) -> None:
def configure_source(build_dir: Path, cmake_flags: str) -> None:
logging.info(f"CMake flags are {cmake_flags}")
build_dir = (script_path / "build").resolve()
build_dir.mkdir(exist_ok=True)
command = f"cmake -B {build_dir} {cmake_flags}"
with open("cmake_build_command.log", "w") as f:
f.write(command)
logging.info(f"BUILD COMMAND is {command}")
logging.info(f"Build command is saved in {script_path}/cmake_build_command.log")
logging.info(f"Build command is saved in {Path.cwd().resolve()}/cmake_build_command.log")
subprocess.run(command.split(), check=True)
logging.info("Successfully configured the source code.")


def build(args: "argparse.Namespace") -> None:
def build(args: "argparse.Namespace", build_dir: Path) -> None:
num_of_process = args.parallel
if num_of_process == -1:
# const value of -j option, which means the native build tool's default number is used.
# (https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-build-j)
cmd = "cmake --build build -j"
cmd = f"cmake --build {build_dir} -j"
else:
cmd = f"cmake --build build -j {num_of_process}"
cmd = f"cmake --build {build_dir} -j {num_of_process}"
subprocess.run(cmd.split(), check=True)
logging.info("Successfully built the project.")


def main():

logging.info("Start dirac_caspt2 setup script.")
exec_path = Path.cwd().resolve()
script_path = Path(__file__).parent.resolve()
os.chdir(script_path)

args = define_args()
cmake_flags = preprocess(args, exec_path, script_path)
configure_source(cmake_flags, script_path)
build_dir = Path(args.build_dir).expanduser().resolve()
validate_build_dir(build_dir, script_path)
logging.info(f"build path is {build_dir}")
cmake_flags = preprocess(args, build_dir)
configure_source(build_dir, cmake_flags)

if not args.build:
finish_message = "Finished the setup script.\n\n If you want to build the project, please run the following command: \n\tcmake --build build\n or\n\tmake -C build\n After building the project successfully, you should run tests!\n"
else:
build(args)
build(args, build_dir)
finish_message = "Finished the setup script.\n"
logging.info(finish_message)

mpi_option_message = " --mpi=[number of MPI processes]" if args.mpi else ""
omp_option_message = " --omp=[number of OpenMP threads per one process]" if args.omp else ""
build_dir_option_message = f" --build-dir={build_dir}"
enable_or_disable_mpi = "set" if args.mpi else "do not set"

logging.info(
Expand All @@ -189,9 +198,9 @@ def main():
\tpip install pytest\n\n\
You {enable_or_disable_mpi} --mpi option. Therefore, you should run one of the following tests:\n\
- Run all tests:\n\
\tpytest --all{mpi_option_message}{omp_option_message}\n\
\tpytest --all{build_dir_option_message}{mpi_option_message}{omp_option_message}\n\
- Run tests excluding the tests long time to run:\n\
\tpytest{mpi_option_message}{omp_option_message}\n\
\tpytest{build_dir_option_message}{mpi_option_message}{omp_option_message}\n\
For more information, please read the {script_path}/README.md file."
)

Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,7 @@ add_executable(r4dcaspt2exe
)
target_link_libraries(r4dcaspt2exe dcaspt2_module)

# r4dcaspt2exe is placed in the PROJECT_BINARY_DIR on build time
set_target_properties(r4dcaspt2exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

install(TARGETS r4dcaspt2exe RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
1 change: 1 addition & 0 deletions test/unit_test/lowercase/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ add_executable(test_lowercase_exe
test_lowercase.f90
)
target_link_libraries(test_lowercase_exe dcaspt2_module)
set_target_properties(test_lowercase_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
6 changes: 3 additions & 3 deletions test/unit_test/lowercase/test_lowercase.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@


@pytest.mark.dev
def test_lowercase():
def test_lowercase(env_setup_unittest):
exe_file_path = env_setup_unittest("test_lowercase_exe")
print(f"Test command: {exe_file_path}")

# Set file names
ref_output_file = "expected" # Reference
output_filename = "result.out" # Output (This file is compared with Reference)
latest_passed_output = "latest_passed.result.out" # latest passed output (After test, the output file is moved to this)
exe_filename = "test_lowercase_exe" # Executable file

# Get this files path and change directory to this path
test_path = os.path.dirname(os.path.abspath(__file__)) # The path of this file
Expand All @@ -28,7 +29,6 @@ def test_lowercase():
ref_output_file_path = os.path.abspath(os.path.join(test_path, ref_output_file))
output_file_path = os.path.abspath(os.path.join(test_path, output_filename))
latest_passed_path = os.path.abspath(os.path.join(test_path, latest_passed_output))
exe_file_path = os.path.abspath(os.path.join(test_path, exe_filename))

is_binary_file_exist(exe_file_path)
delete_scratch_files([output_filename], test_path)
Expand Down
1 change: 1 addition & 0 deletions test/unit_test/ras3_bitcheck/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ add_executable(ras3_bitcheck_exe
test_ras3_bitcheck.f90
)
target_link_libraries(ras3_bitcheck_exe dcaspt2_module)
set_target_properties(ras3_bitcheck_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
5 changes: 2 additions & 3 deletions test/unit_test/ras3_bitcheck/test_ras3_bitcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@


@pytest.mark.dev
def test_ras3_bitcheck():
def test_ras3_bitcheck(env_setup_unittest):
exe_file_path = env_setup_unittest("ras3_bitcheck_exe")
# Current path
test_path = os.path.dirname(os.path.abspath(__file__))

Expand All @@ -19,12 +20,10 @@ def test_ras3_bitcheck():
# input/output/executable file names
ref_output_file = "expected"
result_filename = "result.out"
exe_filename = "ras3_bitcheck_exe"

# Absolute path to input/output/executable files
ref_output_file_path = os.path.abspath(os.path.join(test_path, ref_output_file))
result_file_path = os.path.abspath(os.path.join(test_path, result_filename))
exe_file_path = os.path.abspath(os.path.join(test_path, exe_filename))

is_binary_file_exist(exe_file_path)
test_command = create_test_command(mpi_num_process=1, binaries=[exe_file_path])
Expand Down
1 change: 1 addition & 0 deletions test/unit_test/ras_input_reader/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ add_executable(test_ras_input_reader_exe
test_ras_input_reader.f90
)
target_link_libraries(test_ras_input_reader_exe dcaspt2_module)
set_target_properties(test_ras_input_reader_exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
Loading

0 comments on commit 03b5f9e

Please sign in to comment.