summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@linux.dev>2022-09-21 23:08:33 -0400
committerKent Overstreet <kent.overstreet@linux.dev>2022-09-22 15:43:43 -0400
commit06b890f10a0d602814086304953d5bc70eb22b62 (patch)
tree5ef2d4a980150fe5fb3de7f94b8a8964828f7294
parenta4c7c97110bc51346ee0409e620c9e0f81fbda24 (diff)
get-test-job is now written in rustci-rust
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
-rw-r--r--ci-web/Cargo.toml12
-rw-r--r--ci-web/commit-filter (renamed from ci/commit-filter)0
-rw-r--r--ci-web/src/get-test-job.rs218
-rw-r--r--ci-web/src/lib.rs90
-rw-r--r--ci-web/src/main.rs124
-rw-r--r--ci/_test-git-branch.sh2
-rwxr-xr-xci/get-test-job.sh10
-rw-r--r--lib/Makefile1
-rw-r--r--lib/get-test-job.c385
9 files changed, 336 insertions, 506 deletions
diff --git a/ci-web/Cargo.toml b/ci-web/Cargo.toml
index 1b8b1d6..c247c34 100644
--- a/ci-web/Cargo.toml
+++ b/ci-web/Cargo.toml
@@ -1,12 +1,20 @@
[package]
-name = "ci-web"
+name = "ci-cgi"
version = "0.1.0"
edition = "2021"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[[bin]]
+name = "get-test-job"
+path = "src/get-test-job.rs"
+
+#[workspace]
+#members = ["get-test-job", "ci-cgi"]
[dependencies]
cgi = "0.6"
git2 = "0.14"
querystring = "1.1.0"
dirs = "4.0.0"
+multimap = "0.8.3"
+die = "0.2.0"
+libc = "0.2"
diff --git a/ci/commit-filter b/ci-web/commit-filter
index c2abea9..c2abea9 100644
--- a/ci/commit-filter
+++ b/ci-web/commit-filter
diff --git a/ci-web/src/get-test-job.rs b/ci-web/src/get-test-job.rs
new file mode 100644
index 0000000..e3db5d3
--- /dev/null
+++ b/ci-web/src/get-test-job.rs
@@ -0,0 +1,218 @@
+extern crate libc;
+use std::fs::{OpenOptions, create_dir_all};
+use std::os::unix::fs::OpenOptionsExt;
+use std::io::ErrorKind;
+use std::path::Path;
+use std::process;
+mod lib;
+use lib::{Ktestrc, read_lines, ktestrc_read, git_get_commit};
+
+extern crate multimap;
+use multimap::MultiMap;
+use die::die;
+
+fn get_subtests(test_path: &str) -> Vec<String> {
+ let test_name = Path::new(test_path).file_stem();
+
+ if let Some(test_name) = test_name {
+ let test_name = test_name.to_string_lossy();
+
+ let output = std::process::Command::new(&test_path)
+ .arg("list-tests")
+ .output()
+ .expect(&format!("failed to execute process {:?} ", &test_path))
+ .stdout;
+ let output = String::from_utf8_lossy(&output);
+
+ output
+ .split_whitespace()
+ .map(|i| format!("{}.{}", test_name, i))
+ .collect()
+ } else {
+ Vec::new()
+ }
+}
+
+fn lockfile_exists(rc: &Ktestrc, commit: &str, subtest: &str, create: bool) -> bool {
+ fn test_or_create(lockfile: &Path, create: bool) -> bool {
+ if !create {
+ lockfile.exists()
+ } else {
+ let dir = lockfile.parent();
+ let r = create_dir_all(dir.unwrap());
+
+ if let Err(e) = r {
+ if e.kind() != ErrorKind::AlreadyExists {
+ die!("error creating {:?}: {}", dir, e);
+ }
+ }
+
+ let mut options = OpenOptions::new();
+ options.write(true);
+ options.custom_flags(libc::O_CREAT);
+ options.open(lockfile).is_ok()
+ }
+ }
+
+ let lockfile = rc.ci_output_dir.join(commit).join(subtest);
+ let mut exists = test_or_create(&lockfile, create);
+
+ if exists {
+ let timeout = std::time::Duration::new(3600, 0);
+ let now = std::time::SystemTime::now();
+ let metadata = std::fs::metadata(&lockfile).unwrap();
+
+ if metadata.len() == 0&&
+ metadata.is_file() &&
+ metadata.modified().unwrap() + timeout < now &&
+ std::fs::remove_file(&lockfile).is_ok() {
+ exists = test_or_create(&lockfile, create);
+ }
+ }
+
+ exists
+}
+
+struct TestJob {
+ branch: String,
+ commit: String,
+ age: usize,
+ test: String,
+ subtests: Vec<String>,
+}
+
+fn branch_get_next_test_job(rc: &Ktestrc, repo: &git2::Repository,
+ branch: &str, test_path: &str) -> Option<TestJob> {
+ let mut ret = TestJob {
+ branch: branch.to_string(),
+ commit: String::new(),
+ age: 0,
+ test: test_path.to_string(),
+ subtests: Vec::new(),
+ };
+
+ let subtests = get_subtests(test_path);
+
+ let mut walk = repo.revwalk().unwrap();
+ let reference = git_get_commit(&repo, branch.to_string());
+ if reference.is_err() {
+ eprintln!("branch {} not found", branch);
+ return None;
+ }
+ let reference = reference.unwrap();
+
+ if let Err(e) = walk.push(reference.id()) {
+ eprintln!("Error walking {}: {}", branch, e);
+ return None;
+ }
+
+ for commit in walk
+ .filter_map(|i| i.ok())
+ .filter_map(|i| repo.find_commit(i).ok()) {
+ let commit = commit.id().to_string();
+ ret.commit = commit.clone();
+
+ for subtest in subtests.iter() {
+ if !lockfile_exists(rc, &commit, subtest, false) {
+ ret.subtests.push(subtest.to_string());
+ if ret.subtests.len() > 20 {
+ break;
+ }
+ }
+ }
+
+ if !ret.subtests.is_empty() {
+ return Some(ret);
+ }
+
+ ret.age += 1;
+ }
+
+ None
+}
+
+fn get_best_test_job(rc: &Ktestrc, repo: &git2::Repository,
+ branch_tests: &MultiMap<String, String>) -> Option<TestJob> {
+ let mut ret: Option<TestJob> = None;
+
+ for (branch, testvec) in branch_tests.iter_all() {
+ for test in testvec {
+ let job = branch_get_next_test_job(rc, repo, branch, test);
+
+ if let Some(job) = job {
+ match &ret {
+ Some(r) => if r.age > job.age { ret = Some(job) },
+ None => ret = Some(job),
+ }
+ }
+ }
+ }
+
+ ret
+}
+
+fn create_job_lockfiles(rc: &Ktestrc, mut job: TestJob) -> Option<TestJob> {
+ job.subtests = job.subtests.iter()
+ .filter(|i| lockfile_exists(rc, &job.commit, &i, true))
+ .map(|i| i.to_string())
+ .collect();
+
+ if !job.subtests.is_empty() { Some(job) } else { None }
+}
+
+fn main() {
+ let ktestrc = ktestrc_read();
+
+ let repo = git2::Repository::open(&ktestrc.ci_linux_repo);
+ if let Err(e) = repo {
+ eprintln!("Error opening {:?}: {}", ktestrc.ci_linux_repo, e);
+ eprintln!("Please specify correct JOBSERVER_LINUX_DIR");
+ process::exit(1);
+ }
+ let repo = repo.unwrap();
+
+ let lines = read_lines(&ktestrc.ci_branches_to_test);
+ if let Err(e) = lines {
+ eprintln!("Error opening {:?}: {}", ktestrc.ci_branches_to_test, e);
+ eprintln!("Please specify correct JOBSERVER_BRANCHES_TO_TEST");
+ process::exit(1);
+ }
+ let lines = lines.unwrap();
+
+ let lines = lines.filter_map(|i| i.ok());
+
+ let mut branch_tests: MultiMap<String, String> = MultiMap::new();
+
+ for l in lines {
+ let l: Vec<_> = l.split_whitespace().take(2).collect();
+
+ if l.len() == 2 {
+ let branch = l[0];
+ let test = l[1];
+ branch_tests.insert(branch.to_string(), test.to_string());
+ }
+ }
+
+ let mut job: Option<TestJob>;
+
+ loop {
+ job = get_best_test_job(&ktestrc, &repo, &branch_tests);
+
+ if job.is_none() {
+ break;
+ }
+
+ job = create_job_lockfiles(&ktestrc, job.unwrap());
+ if job.is_some() {
+ break;
+ }
+ }
+
+ if let Some(job) = job {
+ print!("{} {} {}", job.branch, job.commit, job.test);
+ for t in job.subtests {
+ print!(" {}", t);
+ }
+ println!("");
+ }
+}
diff --git a/ci-web/src/lib.rs b/ci-web/src/lib.rs
new file mode 100644
index 0000000..9a8b40d
--- /dev/null
+++ b/ci-web/src/lib.rs
@@ -0,0 +1,90 @@
+use std::fs::File;
+use std::io::{self, BufRead};
+use std::path::{Path, PathBuf};
+extern crate dirs;
+
+pub fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
+where P: AsRef<Path>, {
+ let file = File::open(filename)?;
+ Ok(io::BufReader::new(file).lines())
+}
+
+pub fn git_get_commit(repo: &git2::Repository, reference: String) -> Result<git2::Commit, git2::Error> {
+ let r = repo.revparse_single(&reference);
+ if let Err(e) = r {
+ eprintln!("Error from resolve_reference_from_short_name {} in {}: {}", reference, repo.path().display(), e);
+ return Err(e);
+ }
+
+ let r = r.unwrap().peel_to_commit();
+ if let Err(e) = r {
+ eprintln!("Error from peel_to_commit {} in {}: {}", reference, repo.path().display(), e);
+ return Err(e);
+ }
+ r
+}
+
+pub struct Ktestrc {
+ pub ci_linux_repo: PathBuf,
+ pub ci_output_dir: PathBuf,
+ pub ci_branches_to_test: PathBuf,
+}
+
+pub fn ktestrc_read() -> Ktestrc {
+ let mut ktestrc = Ktestrc {
+ ci_linux_repo: PathBuf::new(),
+ ci_output_dir: PathBuf::new(),
+ ci_branches_to_test: PathBuf::new(),
+ };
+
+ if let Some(home) = dirs::home_dir() {
+ ktestrc.ci_branches_to_test = home.join("BRANCHES-TO-TEST");
+ }
+
+ fn ktestrc_get(rc: &'static str, var: &'static str) -> Option<PathBuf> {
+ let cmd = format!(". {}; echo -n ${}", rc, var);
+
+ let output = std::process::Command::new("/usr/bin/env")
+ .arg("bash")
+ .arg("-c")
+ .arg(&cmd)
+ .output()
+ .expect("failed to execute process /bin/sh")
+ .stdout;
+
+ let output = String::from_utf8_lossy(&output);
+ let output = output.trim();
+
+ if !output.is_empty() {
+ Some(PathBuf::from(output))
+ } else {
+ None
+ }
+ }
+
+ if let Some(v) = ktestrc_get("/etc/ktestrc", "JOBSERVER_LINUX_DIR") {
+ ktestrc.ci_linux_repo = v;
+ }
+
+ if let Some(v) = ktestrc_get("/etc/ktestrc", "JOBSERVER_OUTPUT_DIR") {
+ ktestrc.ci_output_dir = v;
+ }
+
+ if let Some(v) = ktestrc_get("/etc/ktestrc", "JOBSERVER_BRANCHES_TO_TEST") {
+ ktestrc.ci_branches_to_test = v;
+ }
+
+ if let Some(v) = ktestrc_get("$HOME/.ktestrc", "JOBSERVER_LINUX_DIR") {
+ ktestrc.ci_linux_repo = v;
+ }
+
+ if let Some(v) = ktestrc_get("$HOME/.ktestrc", "JOBSERVER_OUTPUT_DIR") {
+ ktestrc.ci_output_dir = v;
+ }
+
+ if let Some(v) = ktestrc_get("$HOME/.ktestrc", "JOBSERVER_BRANCHES_TO_TEST") {
+ ktestrc.ci_branches_to_test = v;
+ }
+
+ ktestrc
+}
diff --git a/ci-web/src/main.rs b/ci-web/src/main.rs
index 230689a..a18678e 100644
--- a/ci-web/src/main.rs
+++ b/ci-web/src/main.rs
@@ -1,14 +1,14 @@
-use git2::Repository;
use std::fmt::Write;
use std::fs::File;
-use std::io::{self, Read, BufRead};
-use std::path::{Path, PathBuf};
-use std::process::Command;
+use std::io::Read;
+use std::path::Path;
extern crate cgi;
-extern crate dirs;
extern crate querystring;
-const COMMIT_FILTER: &str = include_str!("../../ci/commit-filter");
+mod lib;
+use lib::*;
+
+const COMMIT_FILTER: &str = include_str!("../commit-filter");
const STYLESHEET: &str = "/bootstrap.min.css";
fn read_file(f: &Path) -> Option<String> {
@@ -18,93 +18,6 @@ fn read_file(f: &Path) -> Option<String> {
Some(ret)
}
-fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
-where P: AsRef<Path>, {
- let file = File::open(filename)?;
- Ok(io::BufReader::new(file).lines())
-}
-
-fn git_get_commit(repo: &git2::Repository, reference: String) -> Result<git2::Commit, git2::Error> {
- let r = repo.revparse_single(&reference);
- if let Err(e) = r {
- eprintln!("Error from resolve_reference_from_short_name {} in {}: {}", reference, repo.path().display(), e);
- return Err(e);
- }
-
- let r = r.unwrap().peel_to_commit();
- if let Err(e) = r {
- eprintln!("Error from peel_to_commit {} in {}: {}", reference, repo.path().display(), e);
- return Err(e);
- }
- r
-}
-
-struct Ktestrc {
- ci_linux_repo: PathBuf,
- ci_output_dir: PathBuf,
- ci_branches_to_test: PathBuf,
-}
-
-fn ktestrc_read() -> Ktestrc {
- let mut ktestrc = Ktestrc {
- ci_linux_repo: PathBuf::new(),
- ci_output_dir: PathBuf::new(),
- ci_branches_to_test: PathBuf::new(),
- };
-
- if let Some(home) = dirs::home_dir() {
- ktestrc.ci_branches_to_test = home.join("BRANCHES-TO-TEST");
- }
-
- fn ktestrc_get(rc: &'static str, var: &'static str) -> Option<PathBuf> {
- let cmd = format!(". {}; echo -n ${}", rc, var);
-
- let output = Command::new("sh")
- .arg("-c")
- .arg(&cmd)
- .output()
- .expect("failed to execute process /bin/sh")
- .stdout;
-
- let output = String::from_utf8_lossy(&output);
- let output = output.trim();
-
- if !output.is_empty() {
- Some(PathBuf::from(output))
- } else {
- None
- }
- }
-
- if let Some(v) = ktestrc_get("/etc/ktestrc", "JOBSERVER_LINUX_DIR") {
- ktestrc.ci_linux_repo = v;
- }
-
- if let Some(v) = ktestrc_get("/etc/ktestrc", "JOBSERVER_OUTPUT_DIR") {
- ktestrc.ci_output_dir = v;
- }
-
- if let Some(v) = ktestrc_get("/etc/ktestrc", "JOBSERVER_BRANCHES_TO_TEST") {
- ktestrc.ci_branches_to_test = v;
- }
-
- /*
- if let Some(v) = ktestrc_get("$HOME/.ktestrc", "JOBSERVER_LINUX_DIR") {
- ktestrc.ci_linux_repo = v;
- }
-
- if let Some(v) = ktestrc_get("$HOME/.ktestrc", "JOBSERVER_OUTPUT_DIR") {
- ktestrc.ci_output_dir = v;
- }
-
- if let Some(v) = ktestrc_get("$HOME/.ktestrc", "JOBSERVER_BRANCHES_TO_TEST") {
- ktestrc.ci_branches_to_test = v;
- }
- */
-
- ktestrc
-}
-
#[derive(PartialEq)]
enum TestStatus {
InProgress,
@@ -325,21 +238,18 @@ fn ci_list_branches(ci: &Ci) -> cgi::Response {
writeln!(&mut out, "<body>").unwrap();
writeln!(&mut out, "<table class=\"table\">").unwrap();
- if let Ok(lines) = read_lines(&ci.ktestrc.ci_branches_to_test) {
- let branches: std::collections::HashSet<_> = lines
- .filter_map(|i| i.ok())
- .map(|i| if let Some(w) = i.split_whitespace().nth(0) { Some(String::from(w)) } else { None })
- .filter_map(|i| i)
- .collect();
+ let lines = read_lines(&ci.ktestrc.ci_branches_to_test).unwrap();
+ let branches: std::collections::HashSet<_> = lines
+ .filter_map(|i| i.ok())
+ .map(|i| if let Some(w) = i.split_whitespace().nth(0) { Some(String::from(w)) } else { None })
+ .filter_map(|i| i)
+ .collect();
- let mut branches: Vec<_> = branches.iter().collect();
- branches.sort();
+ let mut branches: Vec<_> = branches.iter().collect();
+ branches.sort();
- for b in branches {
- writeln!(&mut out, "<tr> <th> <a href={}?log={}>{}</a> </th> </tr>", ci.script_name, b, b).unwrap();
- }
- } else {
- writeln!(&mut out, "(BRANCHES-TO-TEST not found)").unwrap();
+ for b in branches {
+ writeln!(&mut out, "<tr> <th> <a href={}?log={}>{}</a> </th> </tr>", ci.script_name, b, b).unwrap();
}
writeln!(&mut out, "</table>").unwrap();
@@ -383,7 +293,7 @@ cgi::cgi_main! {|request: cgi::Request| -> cgi::Response {
ktestrc.ci_branches_to_test.as_os_str()));
}
- let repo = Repository::open(&ktestrc.ci_linux_repo).unwrap();
+ let repo = git2::Repository::open(&ktestrc.ci_linux_repo).unwrap();
let ci = Ci {
ktestrc: ktestrc,
diff --git a/ci/_test-git-branch.sh b/ci/_test-git-branch.sh
index 05886a7..c28aa20 100644
--- a/ci/_test-git-branch.sh
+++ b/ci/_test-git-branch.sh
@@ -30,7 +30,7 @@ sync_git_repos()
echo "Getting test job"
-TEST_JOB=( $(ssh $JOBSERVER get-test-job.sh) )
+TEST_JOB=( $(ssh $JOBSERVER get-test-job) )
BRANCH=${TEST_JOB[0]}
COMMIT=${TEST_JOB[1]}
diff --git a/ci/get-test-job.sh b/ci/get-test-job.sh
deleted file mode 100755
index bdcb888..0000000
--- a/ci/get-test-job.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-[[ -f ~/.ktestrc ]] && . ~/.ktestrc
-
-cd $JOBSERVER_HOME/linux
-flock --nonblock .git_fetch.lock git fetch --all > /dev/null
-
-make -C ~/ktest/lib get-test-job 1>&2
-
-~/ktest/lib/get-test-job -b ~/BRANCHES-TO-TEST -o $JOBSERVER_OUTPUT_DIR
diff --git a/lib/Makefile b/lib/Makefile
index 62b5682..f776db5 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,5 +1,4 @@
CFLAGS=-Wall -g -O2
supervisor:
-get-test-job:
lwip-connect: LDLIBS=-llwipv6
diff --git a/lib/get-test-job.c b/lib/get-test-job.c
deleted file mode 100644
index a5245b7..0000000
--- a/lib/get-test-job.c
+++ /dev/null
@@ -1,385 +0,0 @@
-#define _GNU_SOURCE
-
-#include <ctype.h>
-#include <getopt.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#define HAVE_STATEMENT_EXPR 1
-#include "darray/darray.h"
-
-static char *outdir = NULL;
-static char *branches_to_test = NULL;
-static bool verbose = false;
-
-#define die(msg, ...) \
-do { \
- fprintf(stderr, msg "\n", ##__VA_ARGS__); \
- exit(EXIT_FAILURE); \
-} while (0)
-
-static char *mprintf(const char *fmt, ...)
-{
- va_list args;
- char *str;
- int ret;
-
- va_start(args, fmt);
- ret = vasprintf(&str, fmt, args);
- va_end(args);
-
- if (ret < 0)
- die("insufficient memory");
-
- return str;
-}
-
-static void strim(char *line)
-{
- char *p = line;
-
- while (isalnum(*p))
- p++;
- *p = 0;
-}
-
-static char *test_basename(const char *str)
-{
- char *p = strrchr(str, '/');
- char *ret = strdup(p ? p + 1 : str);
-
- p = strstr(ret, ".ktest");
- if (p)
- *p = 0;
- return ret;
-}
-
-typedef darray(char *) strings;
-
-static void strings_free(strings *strs)
-{
- char **s;
-
- darray_foreach(s, *strs)
- free(*s);
- darray_free(*strs);
-
- memset(strs, 0, sizeof(*strs));
-}
-
-typedef struct {
- char *branch;
- char *commit;
- unsigned age;
- char *test;
- strings subtests;
-} test_job;
-
-static void test_job_free(test_job *job)
-{
- free(job->branch);
- free(job->commit);
- free(job->test);
- strings_free(&job->subtests);
- memset(job, 0, sizeof(*job));
-}
-
-static void test_job_print(test_job job)
-{
- fprintf(stderr, "%s %s %s age %u subtests",
- job.branch, job.commit, job.test, job.age);
-
- char **subtest;
- darray_foreach(subtest, job.subtests)
- fprintf(stderr, " %s", *subtest);
- fprintf(stderr, "\n");
-}
-
-static strings get_subtests(char *test_path)
-{
- darray_char output;
- strings ret;
- size_t bytes_read;
-
- darray_init(output);
- darray_init(ret);
-
- if (verbose)
- fprintf(stderr, "Getting subtests for %s\n", test_path);
-
- char *cmd = mprintf("%s list-tests", test_path);
- FILE *f = popen(cmd, "r");
- free(cmd);
-
- if (!f)
- die("error executing %s", test_path);
-
- do {
- darray_make_room(output, 4096);
-
- bytes_read = fread(output.item + output.size,
- 1, 4095, f);
- output.size += bytes_read;
- } while (bytes_read);
-
- pclose(f);
-
- output.item[output.size] = '\0';
-
- char *subtest, *p = output.item;
- while ((subtest = strtok(p, " \t\n"))) {
- darray_push(ret, strdup(subtest));
- p = NULL;
- }
-
- darray_free(output);
-
- if (darray_empty(ret))
- die("error getting subtests from %s", test_path);
-
- return ret;
-}
-
-static char *slashes_to_dots(const char *str)
-{
- char *p, *ret = strdup(str);
-
- while ((p = strchr(ret, '/')))
- *p = '.';
-
- return ret;
-}
-
-static bool __lockfile_exists(const char *commitdir,
- const char *testdir,
- const char *lockfile,
- bool create)
-{
- if (!create) {
- return access(lockfile, F_OK) == 0;
- } else {
- bool exists;
-
- if (mkdir(commitdir, 0755) < 0 && errno != EEXIST)
- die("error creating %s", commitdir);
-
- if (mkdir(testdir, 0755) < 0 && errno != EEXIST)
- die("error creating %s", testdir);
-
- int fd = open(lockfile, O_RDWR|O_CREAT|O_EXCL, 0644);
- exists = fd < 0;
- if (!exists)
- close(fd);
-
- return exists;
- }
-}
-
-static bool lockfile_exists(const char *commit,
- const char *test_path,
- const char *subtest,
- bool create)
-{
- char *test_name = test_basename(test_path);
- char *subtest_mangled = slashes_to_dots(subtest);
- char *commitdir = mprintf("%s/%s", outdir, commit);
- char *testdir = mprintf("%s/%s.%s", commitdir, test_name, subtest_mangled);
- char *lockfile = mprintf("%s/status", testdir);
- struct timeval now;
- struct stat statbuf;
- bool exists;
-
- gettimeofday(&now, NULL);
-
- exists = __lockfile_exists(commitdir, testdir, lockfile, create);
-
- if (exists &&
- !stat(lockfile, &statbuf) &&
- !statbuf.st_size &&
- S_ISREG(statbuf.st_mode) &&
- statbuf.st_ctime + 60 * 60 < now.tv_sec &&
- !unlink(lockfile)) {
- fprintf(stderr, "Deleting stale test job %s %s %s (%lu minutes old)\n",
- commit, test_name, subtest,
- (now.tv_sec - statbuf.st_ctime) / 60);
- exists = false;
- }
-
- free(lockfile);
- free(testdir);
- free(commitdir);
- free(subtest_mangled);
- free(test_name);
-
- return exists;
-}
-
-static test_job branch_get_next_test_job(char *branch,
- char *test_path,
- strings subtests)
-{
- char *cmd = mprintf("git log --pretty=format:%H %s", branch);
- FILE *commits = popen(cmd, "r");
- char *commit = NULL;
- size_t n = 0;
- ssize_t len;
- test_job ret;
-
- memset(&ret, 0, sizeof(ret));
-
- while ((len = getline(&commit, &n, commits)) >= 0) {
- strim(commit);
-
- char **subtest;
- darray_foreach(subtest, subtests)
- if (!lockfile_exists(commit, test_path, *subtest, false)) {
- darray_push(ret.subtests, strdup(*subtest));
- if (darray_size(ret.subtests) > 20)
- break;
- }
-
- if (!darray_empty(ret.subtests)) {
- ret.branch = strdup(branch);
- ret.commit = strdup(commit);
- ret.test = strdup(test_path);
- goto success;
- }
-
- ret.age++;
- }
- fprintf(stderr, "error looking up commits on branch %s\n", branch);
-success:
- pclose(commits);
- free(commit);
- free(cmd);
- return ret;
-}
-
-static test_job get_best_test_job()
-{
- FILE *branches = fopen(branches_to_test, "r");
- char *line = NULL;
- size_t n = 0;
- ssize_t len;
- test_job best;
-
- memset(&best, 0, sizeof(best));
-
- if (!branches)
- die("error opening %s: %m", branches_to_test);
-
- while ((len = getline(&line, &n, branches)) >= 0) {
- char *branch = strtok(line, " \t\n");
- char *test_path = strtok(NULL, " \t\n");
-
- if (!branch || !test_path)
- continue;
-
- if (verbose)
- fprintf(stderr, "get_best_test_job: checking branch %s test %s\n",
- branch, test_path);
-
- strings subtests = get_subtests(test_path);
-
- test_job job = branch_get_next_test_job(branch, test_path, subtests);
-
- strings_free(&subtests);
-
- if (!best.branch || job.age < best.age) {
- test_job_free(&best);
- best = job;
- } else {
- test_job_free(&job);
- }
- }
-
- if (!best.branch)
- die("Nothing found");
-
- if (verbose) {
- fprintf(stderr, "get_best_test_job: best ");
- test_job_print(best);
- }
-
- fclose(branches);
- free(line);
- return best;
-}
-
-void usage(void)
-{
- puts("get-test-job: get a test job and create lockfile\n"
- "Usage: get-test-job [OPTIONS]\n"
- "\n"
- "Options\n"
- " -b file List of branches to test and tests to run\n"
- " -o dir Directory for tests results\n"
- " -v Verbose\n"
- " -h Display this help and exit");
- exit(EXIT_SUCCESS);
-}
-
-int main(int argc, char *argv[])
-{
- int opt;
- test_job job;
- strings subtests;
- char **subtest;
-
- darray_init(subtests);
- memset(&job, 0, sizeof(job));
-
- while ((opt = getopt(argc, argv, "b:o:vh")) != -1) {
- switch (opt) {
- case 'b':
- branches_to_test = strdup(optarg);
- break;
- case 'o':
- outdir = strdup(optarg);
- break;
- case 'v':
- verbose = true;
- break;
- case 'h':
- usage();
- exit(EXIT_SUCCESS);
- case '?':
- usage();
- exit(EXIT_FAILURE);
- }
- }
-
- if (!branches_to_test || !outdir)
- die("required argument missing");
-
- do {
- test_job_free(&job);
- job = get_best_test_job();
-
- darray_free(subtests);
- darray_init(subtests);
-
- darray_foreach(subtest, job.subtests)
- if (!lockfile_exists(job.commit, job.test, *subtest, true))
- darray_push(subtests, *subtest);
- } while (darray_empty(subtests));
-
- printf("%s %s %s", job.branch, job.commit, job.test);
- darray_foreach(subtest, subtests)
- printf(" %s", *subtest);
- printf("\n");
-
- test_job_free(&job);
- darray_free(subtests);
- free(outdir);
- free(branches_to_test);
-}