Skip to content

Commit c8e8fcd

Browse files
committed
test(aot): adds bash test suite for ahead of time compilation
1 parent fd6fcee commit c8e8fcd

File tree

17 files changed

+252
-20
lines changed

17 files changed

+252
-20
lines changed

compiler+runtime/include/cpp/jank/util/cli.hpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,6 @@ namespace jank::util::cli
5454
command command{ command::repl };
5555
};
5656

57-
struct empty_options
58-
{
59-
/* TODO: Use a native_persistent_vector instead. */
60-
std::vector<native_transient_string> opts;
61-
};
62-
6357
jtl::result<options, int> parse(int const argc, char const **argv);
64-
empty_options parse_empty(int const argc, char const **argv);
58+
std::vector<native_transient_string> parse_empty(int const argc, char const **argv);
6559
}

compiler+runtime/src/cpp/jank/aot/processor.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,26 @@ int main(int argc, const char** argv)
169169

170170
for(auto const &module : __rt_ctx->loaded_modules_in_order)
171171
{
172-
auto const &module_path{ module::module_to_path(module) };
173-
Args.push_back(strdup(util::format("{}.o", relative_to_cache_dir(module_path)).c_str()));
172+
auto const &module_path{
173+
util::format("{}.o", relative_to_cache_dir(module::module_to_path(module)))
174+
};
175+
176+
if(std::filesystem::exists(module_path.c_str()))
177+
{
178+
Args.push_back(strdup(module_path.c_str()));
179+
}
180+
else
181+
{
182+
auto const find_res{ __rt_ctx->module_loader.find(module, module::origin::latest) };
183+
if(find_res.is_ok() && find_res.expect_ok().sources.o.is_some())
184+
{
185+
Args.push_back(strdup(find_res.expect_ok().sources.o.unwrap().path.c_str()));
186+
}
187+
else
188+
{
189+
return compiler_err{ 1, util::format("module '{}' not found", module) };
190+
}
191+
}
174192
}
175193

176194
Args.push_back(strdup("-x"));

compiler+runtime/src/cpp/jank/c_api.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -999,9 +999,9 @@ extern "C"
999999
jank_object_ref jank_parse_command_line_args(int const argc, char const **argv)
10001000
{
10011001
obj::transient_vector trans;
1002-
auto const opts{ util::cli::parse_empty(argc, argv).opts };
1002+
auto const args{ util::cli::parse_empty(argc, argv) };
10031003

1004-
for(auto &arg : opts)
1004+
for(auto const &arg : args)
10051005
{
10061006
trans.conj_in_place(make_box(arg));
10071007
}

compiler+runtime/src/cpp/jank/util/cli.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,19 +132,12 @@ namespace jank::util::cli
132132
return ok(opts);
133133
}
134134

135-
empty_options parse_empty(int const argc, char const **argv)
135+
std::vector<native_transient_string> parse_empty(int const argc, char const **argv)
136136
{
137137
CLI::App cli{ "jank default cli" };
138-
empty_options opts;
139138
cli.allow_extras();
140-
141139
cli.parse(argc, argv);
142140

143-
if(cli.remaining_size() > 0)
144-
{
145-
opts.opts = cli.remaining();
146-
}
147-
148-
return opts;
141+
return cli.remaining();
149142
}
150143
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{:paths ["src/common"]
2+
:aliases {:single-jank-module {:extra-paths ["src/single-jank-module"]}
3+
:only-jank-modules {:extra-paths ["src/only-jank-modules"]}
4+
:jank-and-cpp-modules {:extra-paths ["src/jank-and-cpp-modules"]}}}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Loaded 'my-lib
2+
Loaded 'lib
3+
Loaded 'core
4+
Received args: (foo bar)
5+
Hello, foo!
6+
Hello from cpp module, bar!
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Loaded 'lib
2+
Loaded 'app
3+
Loaded 'core
4+
Hello, Admin!
5+
Running app on port: 3000
6+
Bow Bow, Admin please!
7+
Done running the app!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Received args: (foo bar baz)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No args
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env bb
2+
3+
(ns jank.test.ahead-of-time
4+
(:require
5+
[babashka.fs :as fs]
6+
[babashka.process :as proc]
7+
[clojure.string :as str]
8+
[clojure.test :as t :refer [deftest is run-test testing use-fixtures]]))
9+
10+
(def this-nsym (ns-name *ns*))
11+
12+
(def jank-path
13+
(-> (proc/sh "which jank") :out str/trim))
14+
15+
(def jank-build-dir (fs/parent jank-path))
16+
17+
(def lib-paths
18+
;; TODO: These library paths should ideally be included
19+
;; in the AOT program
20+
(let [libs-relative-paths ["/" "/third-party/libzippp"
21+
"/third-party/ftxui"
22+
"/third-party/cpptrace"]]
23+
(->> libs-relative-paths
24+
(map (fn [lib] (str "-L" jank-build-dir lib)))
25+
(str/join " "))))
26+
27+
(def jank_exe
28+
(str jank-path " -DIMMER_HAS_LIBGC " lib-paths))
29+
30+
(defn- module-path [alias]
31+
(-> (proc/sh "clojure" (str "-A:" alias) "-Spath") :out str/trim))
32+
33+
(defn- find-cli []
34+
(-> (proc/sh "find ./target -name cli") :out str/trim))
35+
36+
(defn- get-headers []
37+
;; TODO: These headers should be included by default in the AOT program
38+
;; As of now, we expect the users to include these
39+
(->> (fs/list-dirs [(str jank-build-dir "/../include")
40+
(str jank-build-dir "/../third-party")
41+
(str jank-build-dir "/third-party")]
42+
(constantly true))
43+
(concat ["/usr/lib/clang/19/include" (str jank-build-dir "/../third-party/bpptree/include")])
44+
(map #(str "--include-dir " %))
45+
(str/join " ")))
46+
47+
(defn compile-command [module-path main-module & {:keys [include-headers?]}]
48+
(str jank_exe " --module-path=" module-path
49+
" "
50+
(when include-headers? (get-headers))
51+
" compile " main-module " -o cli"))
52+
53+
(use-fixtures
54+
:each
55+
(fn [f]
56+
(fs/delete-tree "./target")
57+
(println "Cleaned up 'target'")
58+
(f)))
59+
60+
(deftest aot-only-jank-modules
61+
(let [alias-name "single-jank-module"
62+
module-path (module-path alias-name)]
63+
(doseq [main-with-args? [true false]
64+
:let [main-module (if main-with-args?
65+
"main-with-args"
66+
"main-without-args")
67+
args (if main-with-args?
68+
" foo bar baz"
69+
nil)
70+
expected-output (slurp (str "expected_outputs/" alias-name "/" main-module))
71+
compile-command (compile-command module-path main-module)
72+
cli-path (delay (find-cli))]]
73+
(testing (str alias-name " & " main-module)
74+
(is (= 0 (-> compile-command proc/sh :exit)))
75+
(is (= expected-output (-> @cli-path (str args)
76+
proc/sh :out))))
77+
(testing "Executable in temp directory"
78+
(fs/with-temp-dir [dir {}]
79+
(fs/move @cli-path dir)
80+
(is (= expected-output (->> (str "./cli " args)
81+
(proc/sh {:dir dir})
82+
:out))))))))
83+
84+
(deftest aot-multiple-jank-modules
85+
(let [alias-name "only-jank-modules"
86+
module-path (module-path alias-name)
87+
module "core"
88+
args " Admin 3000"
89+
expected-output (slurp (str "expected_outputs/" alias-name "/" module))
90+
compile-command (compile-command module-path module)
91+
cli-path (delay (find-cli))]
92+
(testing (str alias-name " & core")
93+
(is (= 0 (-> compile-command proc/sh :exit)))
94+
(is (= expected-output (-> @cli-path (str args)
95+
proc/sh :out))))))
96+
97+
(deftest aot-jank-and-cpp-modules
98+
(let [alias-name "jank-and-cpp-modules"
99+
module-path (module-path alias-name)
100+
module "core"
101+
args " foo bar"
102+
expected-output (slurp (str "expected_outputs/" alias-name "/" module))
103+
compile-command (compile-command module-path module :include-headers? true)
104+
cli-path (delay (find-cli))]
105+
(testing (str alias-name " & core")
106+
(is (= 0 (-> compile-command proc/sh :exit)))
107+
(is (= expected-output (-> @cli-path (str args)
108+
proc/sh :out))))))
109+
110+
(defn -main []
111+
(System/exit
112+
(if (t/successful? (t/run-tests this-nsym))
113+
0
114+
1)))
115+
116+
(when (= *file* (System/getProperty "babashka.file"))
117+
(apply -main *command-line-args*))
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(ns app (:require [lib]))
2+
3+
(defn run [admin port]
4+
(println "Running app on port:" port)
5+
(lib/bark admin true)
6+
(println "Done running the app!"))
7+
8+
(println "Loaded 'app")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(ns lib)
2+
3+
(defn greet [name]
4+
(println (str "Hello, " name "!")))
5+
6+
(defn bark [name please?]
7+
(println (str "Bow Bow, " name (when please? " please!"))))
8+
9+
(println "Loaded 'lib")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(ns core
2+
(:require [my-lib] [lib]))
3+
4+
(defn -main [& args]
5+
(println "Received args:" args)
6+
(lib/greet (first args))
7+
(println (my-lib/greet-str (second args))))
8+
9+
(println "Loaded 'core")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <jtl/immutable_string.hpp>
2+
3+
#include <clojure/core_native.hpp>
4+
5+
#include <jank/runtime/context.hpp>
6+
#include <jank/runtime/convert/function.hpp>
7+
#include <jank/runtime/core.hpp>
8+
#include <jank/runtime/core/equal.hpp>
9+
#include <jank/runtime/core/meta.hpp>
10+
#include <jank/runtime/behavior/callable.hpp>
11+
#include <jank/runtime/visit.hpp>
12+
#include <jank/util/fmt.hpp>
13+
14+
using jank_object_ref = void *;
15+
16+
using namespace jank;
17+
using namespace jank::runtime;
18+
19+
static object_ref greet_str(object_ref name)
20+
{
21+
auto const s_obj(try_object<obj::persistent_string>(name));
22+
auto const new_str{ "Hello from cpp module, " + s_obj->to_string() + "!"};
23+
return make_box(new_str).erase();
24+
}
25+
26+
extern "C" jank_object_ref jank_load_my_lib()
27+
{
28+
auto const ns_name{ "my-lib" };
29+
auto const ns(__rt_ctx->intern_ns(ns_name));
30+
__rt_ctx->current_ns_var->set(ns).expect_ok();
31+
32+
33+
auto const intern_fn([=](jtl::immutable_string const &name, auto const fn) {
34+
ns->intern_var(name)->bind_root(
35+
make_box<obj::native_function_wrapper>(convert_function(fn))
36+
->with_meta(obj::persistent_hash_map::create_unique(std::make_pair(
37+
__rt_ctx->intern_keyword("name").expect_ok(),
38+
make_box(obj::symbol{ __rt_ctx->current_ns()->to_string(), name }.to_string())))));
39+
});
40+
41+
intern_fn("greet-str", &greet_str);
42+
43+
__rt_ctx->module_loader.set_is_loaded(ns_name);
44+
std::cout<<"Loaded '"<<ns_name<<"\n";
45+
return jank_nil.erase();
46+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(ns core
2+
(:require [app] [lib]))
3+
4+
(defn -main [& [admin port]]
5+
(if (or admin port)
6+
(do
7+
(lib/greet admin)
8+
(app/run admin port))
9+
(println :failed)))
10+
11+
(println "Loaded 'core")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(ns main-with-args)
2+
3+
(defn -main [& args]
4+
(println "Received args:" args))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(ns main-without-args)
2+
3+
(defn -main []
4+
(println "No args"))

0 commit comments

Comments
 (0)