aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@users.noreply.github.com>2020-07-02 17:27:03 -0400
committerGitHub <noreply@github.com>2020-07-02 17:27:03 -0400
commit9c149534389845412fbfb305b8a6f00923009c4b (patch)
tree0bb4cba597691aa9be0dc23bd1270e2bcbfde1bb
parent2df3b35bebdb81c8e45c08d5a7af46e8fa32e07b (diff)
parent288ce22e256f71ba01cea63aa03a4880e5b1612d (diff)
downloadsplitfits-9c149534389845412fbfb305b8a6f00923009c4b.tar.gz
Merge pull request #4 from jhunkeler/circleci-project-setup
Circleci project setup
-rw-r--r--.circleci/config.yml34
-rw-r--r--CMakeLists.txt26
-rw-r--r--README.md4
-rw-r--r--splitfits.c63
-rwxr-xr-xtest.sh181
-rw-r--r--test_config.sh11
-rw-r--r--tests/test_split.sh35
-rw-r--r--tests/test_usage.sh9
-rw-r--r--version.h.in6
9 files changed, 361 insertions, 8 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..e412a11
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,34 @@
+version: 2
+jobs:
+ build:
+ machine:
+ image: ubuntu-1604:202004-01
+
+ steps:
+ - checkout
+ - run: |
+ set -xe
+ mkdir build && cd build
+ cmake ..
+ make
+
+ test:
+ machine:
+ image: ubuntu-1604:202004-01
+
+ steps:
+ - checkout
+ - run: |
+ set -xe
+ mkdir build && cd build
+ cmake ..
+ make
+ make check
+
+workflows:
+ version: 2
+
+ build-and-test:
+ jobs:
+ - build
+ - test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea12358..43f002c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,9 +1,29 @@
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.1...3.16)
project(splitfits C)
-
set(CMAKE_C_STANDARD 99)
+find_package(Git)
+if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
+ execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --long --tags --dirty
+ OUTPUT_VARIABLE GIT_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+else()
+ message(WARNING "git not found or directory is not a repository")
+ set(GIT_VERSION, "unknown")
+endif()
+
add_executable(splitfits splitfits.c)
+include_directories("${CMAKE_CURRENT_BINARY_DIR}")
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.h.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/version.h" @ONLY)
+
+file(GLOB TEST_RUNNER "test*.sh")
+file(COPY ${TEST_RUNNER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+file(GLOB TEST_FRAMEWORK "tests")
+file(COPY ${TEST_FRAMEWORK} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+
+add_custom_target(check "${CMAKE_CURRENT_BINARY_DIR}/test.sh")
install(TARGETS splitfits
- RUNTIME)
+ RUNTIME DESTINATION bin)
diff --git a/README.md b/README.md
index e4ff981..066c951 100644
--- a/README.md
+++ b/README.md
@@ -13,15 +13,19 @@ CFITSIO is a full-featured library and has quite a few system dependencies. `spl
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
+$ make
+$ make check
$ make install
```
## Without cmake
```sh
$ cc -o splitfits splitfits.c
+$ ./test.sh
$ install -m 755 splitfits /usr/local/bin
```
+
# Usage
```
diff --git a/splitfits.c b/splitfits.c
index 7493ed9..6347c4a 100644
--- a/splitfits.c
+++ b/splitfits.c
@@ -4,12 +4,43 @@
#include <limits.h>
#include <unistd.h>
#include <errno.h>
+#include <sys/stat.h>
+#include "version.h"
#define OP_SPLIT 0
#define OP_COMBINE 1
#define FITS_BLOCK 2880
#define FITS_RECORD 80
+static int mkdirs(const char *_path, mode_t mode) {
+ const char *delim;
+ char *parts;
+ char *token;
+ char path[PATH_MAX];
+ int status;
+
+ delim = "/";
+ parts = strdup(_path);
+ if (parts == NULL) {
+ perror(parts);
+ exit(1);
+ }
+
+ memset(path, '\0', PATH_MAX);
+
+ status = -1;
+ token = NULL;
+ while ((token = strsep(&parts, delim)) != NULL) {
+ strcat(path, token);
+ strcat(path, delim);
+ status = mkdir(path, mode);
+ if (status < 0) {
+ break;
+ }
+ }
+ return status;
+}
+
/**
* Check for FITS header keyword in data block
* @param block of memory of size FITS_BLOCK
@@ -151,6 +182,8 @@ int split_file(const char *_filename, const char *dest) {
FILE *fp_out;
FILE *map_out;
char outfile[PATH_MAX];
+ char path[PATH_MAX];
+ char filename[PATH_MAX];
char _mapfile[PATH_MAX];
char *mapfile;
char *block;
@@ -176,7 +209,8 @@ int split_file(const char *_filename, const char *dest) {
}
mapfile = _mapfile;
- sprintf(mapfile, "%s/%s.part_map", dest ? dest : ".", _filename);
+ strcpy(filename, _filename);
+ sprintf(mapfile, "%s/%s.part_map", dest ? dest : ".", get_basename(filename));
block_size = FITS_BLOCK;
dataFrame = dataframe_init();
@@ -241,8 +275,6 @@ int split_file(const char *_filename, const char *dest) {
// Read offset from the input files and write it to its respective .part_N file
for (off = 0; off < dataFrame->num_inuse; off++) {
- char path[PATH_MAX];
- char filename[PATH_MAX];
char *ext;
// Get dirname of input path
@@ -263,7 +295,7 @@ int split_file(const char *_filename, const char *dest) {
}
// Create output file name
- sprintf(outfile, "%s/%s", path, filename);
+ sprintf(outfile, "%s/%s", path, get_basename(filename));
// Strip file extension from output file
if ((ext = strrchr(outfile, '.')) == NULL) {
@@ -455,6 +487,7 @@ void usage(char *program_name) {
printf(" -h --help This message\n");
printf(" -c --combine Reconstruct original file using .part_map data\n");
printf(" -o --outdir Path where output files are stored\n");
+ printf(" -V --version Display version\n");
}
int main(int argc, char *argv[]) {
@@ -485,19 +518,39 @@ int main(int argc, char *argv[]) {
exit(0);
} else if (strcmp(argv[inputs], "-o") == 0 || strcmp(argv[inputs], "--outdir") == 0) {
inputs++;
+ if (argv[inputs] == NULL) {
+ fprintf(stderr, "-o requires an argument\n");
+ usage(prog);
+ exit(1);
+ }
if (access(argv[inputs], R_OK | W_OK | X_OK) != 0) {
- fprintf(stderr, "%s: output directory does not exist or is not writable\n", argv[inputs]);
+ printf("Creating output directory: %s\n", argv[inputs]);
+ if (mkdirs(argv[inputs], 0755) < 0) {
+ perror(outdir);
+ exit(1);
+ }
}
outdir = strdup(argv[inputs]);
} else if (strcmp(argv[inputs], "-c") == 0 || strcmp(argv[inputs], "--combine") == 0) {
// User wants to reconstruct a FITS file using a .part_map
op_mode = OP_COMBINE;
+ } else if (strcmp(argv[inputs], "-V") == 0 || strcmp(argv[inputs], "--version") == 0) {
+ // Dump version and exit
+ puts(SPLITFITS_VERSION);
+ exit(0);
} else {
// Arguments beyond this point are considered input file paths
break;
}
}
+ // Make sure we have at least one file to process
+ if (argc - inputs == 0) {
+ fprintf(stderr, "At least one input FILE is required\n");
+ usage(prog);
+ exit(1);
+ }
+
// Make sure all input files exist
bad_files = 0;
for (size_t i = inputs; i < argc; i++) {
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..99a78de
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,181 @@
+#!/usr/bin/env bash
+set -o pipefail
+
+RTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+
+source "${RTDIR}"/test_config.sh
+
+get_data_exists() {
+ local url
+ local response
+
+ url="${test_data_remote}/$1"
+ if [[ -z $url ]]; then
+ echo "get_data_exists: requires a path relative to $test_data_remote"
+ exit 1
+ fi
+
+ response=$(curl -s --head "$url" | head -n 1 | awk '{print $2}')
+ if (( $response != 200 )); then
+ return 1;
+ fi
+ return 0;
+}
+
+get_data() {
+ local url
+ local filename
+
+ if [[ ! -d "${test_data}" ]]; then
+ mkdir -p "${test_data}"
+ fi
+
+ url="${test_data_remote}/$1"
+ if [[ -z $url ]]; then
+ echo "get_data: requires a path relative to $test_data_remote"
+ exit 1
+ fi
+
+ filename=$(basename $url)
+ if ! get_data_exists "$1"; then
+ echo "${url}: not found on remote server" >&2
+ return 1
+ fi
+
+ if ! (cd $test_data && curl -L -O "$url"); then
+ echo "Could not download data" >&2
+ return 1
+ fi
+
+ echo "$test_data/$filename";
+}
+
+put_data() {
+ local url
+ local filename
+
+ url="${test_data_remote}/$2"
+ filename="$1"
+
+ if [[ -z $url ]]; then
+ echo "put_data: requires a path relative to $test_data_remote"
+ return 1
+ fi
+
+ if ! curl -s --user "${test_data_remote_auth}" --upload-file "${filename}" "${url}/${filename}"; then
+ echo "Could not upload '${filename}' to '${url}'" >&2
+ return 1
+ fi
+
+ return 0
+}
+
+put_result() {
+ local src;
+ local dest;
+ src="$1"
+ dest="$2"
+
+ if [[ ! -d "$src" ]]; then
+ echo "push_result: source directory does not exist: ${src}" >&2
+ return 1
+ fi
+
+ if [[ -z "$dest" ]]; then
+ echo "push_result: requires a destination relative to ${test_data_remote}" >&2
+ return 1
+ fi
+
+ for f in $(find "${src}" -type f); do
+ echo "Uploading '$f' to '$dest'"
+ if ! put_data "$f" "${dest}"; then
+ echo "Failed uploading '$src' to '$dest'" >&2
+ return 1
+ fi
+ done;
+ return 0
+}
+
+read_tests() {
+ for f in "${RTDIR}"/tests/test_*; do
+ source "$f" || return 0
+ done
+ return 1
+}
+
+export_tests() {
+ tests=(
+ $(declare -f | cut -d ' ' -f 1 | grep -E '^test_')
+ )
+ total_passed=0
+ total_failed=0
+ total_tests=${#tests[@]}
+}
+
+run_tests() {
+ for (( i=0; i < ${total_tests}; i++ )); do
+ just_failed=0
+ test_case="${tests[$i]}"
+ rm -rf "${test_case}"
+ mkdir -p "${test_case}"
+ pushd "${test_case}" &>/dev/null
+ log_file="output_${test_case}.log"
+
+ /bin/echo -n -e "[$i] ${test_case}... "
+ "$test_case" 2>&1 | tee ${log_file} &>/dev/null
+ retval=$?
+
+ if [[ "$retval" -ne "0" ]]; then
+ echo "FAILED ($retval)"
+ (( just_failed++ ))
+ (( total_failed++ ))
+ else
+ echo "PASSED"
+ (( total_passed++ ))
+ fi
+
+ if [[ ${just_failed} -ne 0 ]] && [[ -s ${log_file} ]]; then
+ echo "# OUTPUT:"
+ cat "${log_file}"
+ fi
+ popd &>/dev/null
+ done
+
+ # Upload all artifacts in all test directories
+ if [[ -n "$test_data_remote_auth" ]]; then
+ echo
+ for (( i=0; i < ${total_tests}; i++ )); do
+ test_case="${tests[i]}"
+ put_result ${test_case} ${test_data_upload}
+ done
+ fi
+}
+
+check_runtime() {
+ if [[ ! -f "${test_program}" ]]; then
+ echo "Tests cannot run without: ${test_program}" >&2
+ exit 1
+ fi
+}
+
+show_stats() {
+ printf "\n%d test(s): %d passed, %d failed\n" ${total_tests} ${total_passed} ${total_failed}
+}
+
+# main program
+check_runtime
+
+if read_tests; then
+ echo "Failed to aggregate tests." >&2
+ exit 1
+fi
+
+export_tests
+run_tests
+show_stats
+
+if (( total_failed )); then
+ exit 1
+fi
+
+exit 0
diff --git a/test_config.sh b/test_config.sh
new file mode 100644
index 0000000..7746a6d
--- /dev/null
+++ b/test_config.sh
@@ -0,0 +1,11 @@
+test_program="${RTDIR}/splitfits"
+test_program_version="$(git describe --always --tags --long)"
+test_data="${RTDIR}/data"
+test_data_remote=https://nx.astroconda.org/repository
+test_data_remote_auth=${test_data_remote_auth:-}
+test_data_upload="generic/spb-splitfits"
+if [[ $CIRCLECI == "true" ]]; then
+ test_data_upload="${test_data_upload}/ci/${CIRCLE_BRANCH}_${CIRCLE_JOB}/${CIRCLE_BUILD_NUM}/${test_program_version}"
+else
+ test_data_upload="${test_data_upload}/user/${test_program_version}"
+fi
diff --git a/tests/test_split.sh b/tests/test_split.sh
new file mode 100644
index 0000000..141e320
--- /dev/null
+++ b/tests/test_split.sh
@@ -0,0 +1,35 @@
+test_splitfits_data1() {
+ local datafile
+ local retval
+
+ retval=0
+ datafile=$(get_data generic/fits/sample.fits)
+ [[ ! -f ${datafile} ]] && return 1
+
+ if ! ${test_program} -o ${test_case} ${datafile}; then
+ return 1
+ fi
+
+ set +x
+ return $retval
+}
+
+test_splitfits_combine_data1() {
+ local datafile
+ local retval
+
+ retval=0
+ datafile=$(get_data generic/fits/sample.fits)
+ [[ ! -f ${datafile} ]] && return 1
+
+ if ! ${test_program} -o ${test_case} ${datafile}; then
+ return 1
+ fi
+
+ if ! ${test_program} -c ${test_case}/$(basename ${datafile}).part_map; then
+ return 1
+ fi
+
+ set +x
+ return $retval
+}
diff --git a/tests/test_usage.sh b/tests/test_usage.sh
new file mode 100644
index 0000000..2a257d9
--- /dev/null
+++ b/tests/test_usage.sh
@@ -0,0 +1,9 @@
+test_splitfits_usage_longopt() {
+ ${test_program} --help
+ return $?
+}
+
+test_splitfits_usage_shortopt() {
+ ${test_program} -h
+ return $?
+}
diff --git a/version.h.in b/version.h.in
new file mode 100644
index 0000000..581156e
--- /dev/null
+++ b/version.h.in
@@ -0,0 +1,6 @@
+#ifndef SPLITFITS_VERSION_H
+#define SPLITFITS_VERSION_H
+
+#define SPLITFITS_VERSION "@GIT_VERSION@"
+
+#endif