diff --git a/.circleci/config.yml b/.circleci/config.yml index 39eb63d4..bb614ebb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -111,7 +111,7 @@ jobs: command: | cd ~ if ! [ -d graalvm-ce-java11-21.1.0 ]; then - curl -O -sL https://github.com/graalvm/graalvm-ce-dev-builds/releases/download/21.2.0-dev-20210616_2034/graalvm-ce-java11-linux-amd64-dev.tar.gz + curl -O -sL https://github.com/graalvm/graalvm-ce-dev-builds/releases/download/21.2.0-dev-20210712_1204/graalvm-ce-java11-linux-amd64-dev.tar.gz tar xzf graalvm-ce-java11-linux-amd64-dev.tar.gz fi - run: diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a85c1f..3b98c672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,29 @@ For a list of breaking changes, check [here](#breaking-changes). ## Unreleased +Babashka proper: + +- Add `clojure.tools.logging` with `taoensso.timbre` as the default implementation - Passing form on Windows with question mark breaks evaluation [#889](https://github.com/babashka/babashka/issues/889) +- `(read-line)` is buggy in REPL [#899](https://github.com/babashka/babashka/issues/899) +- Add `java.io.FileInputStream`. This fixes compatibility with [replikativ/hasch](https://github.com/replikativ/hasch). +- `babashka.tasks/clojure` with `:dir` option doesn't resolve deps in `:dir` [#914](https://github.com/babashka/babashka/issues/914) +- Support `pprint/formatter-out` [#922](https://github.com/babashka/babashka/issues/922) +- Support `pprint/cl-format` with `with-out-str` [#930](https://github.com/babashka/babashka/issues/930) +- Compatibility with `org.clojure/data.json {:mvn/version "2.4.0}"` +- Support passing `GITLIBS` via `:extra-env` in `clojure` to set git lib dir: + `(clojure {:extra-env {"GITLIBS" ".gitlib"}} ,,,) [#934](https://github.com/babashka/babashka/issues/934)` +- Add `--force` option to force recomputation of bababashka deps classpath. + +Deps.clj: + +Update to v0.0.16 which corresponds to clojure CLI `1.10.3.855`. + +Sci: + +- Perf improvements +- `case` expression generated from macro doesn't work correctly +- Fix stacktrace with invalid import [borkdude/sci#589](https://github.com/borkdude/sci/issues/589) ## 0.4.6 diff --git a/README.md b/README.md index 36363f3e..6bdb3c5e 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,9 @@ handling of SIGINT and SIGPIPE. This can be done by setting ## Articles, podcasts and videos +- [Integrating Babashka into Bazel](https://timjaeger.io/20210627-integrating-babashka-with-bazel.html) by Tim Jäger +- [Talk](https://youtu.be/Yjeh57eE9rg): Babashka: a native Clojure interpreter for scripting — The 2021 Graal Workshop at CGO +- [Blog](https://savo.rocks/posts/playing-new-music-on-old-car-stereo-with-clojure-and-babashka/): Playing New Music On Old Car Stereo With Clojure And Babashka - [Homoiconicity and feature flags](https://martinklepsch.org/posts/homoiconicity-and-feature-flags.html) by Martin Klepsch - [Clojure like its PHP](https://eccentric-j.com/blog/clojure-like-its-php.html) by Jay Zawrotny (eccentric-j) - [Deploy babashka script to AWS Lambda](https://www.jocas.lt/blog/post/babashka-aws-lambda/) by Dainius Jocas. @@ -380,13 +383,18 @@ This project exists thanks to all the people who contribute. [[Contribute](doc/d ### Financial Contributors +#### Github Sponsors + +- [Dig Gashinsky](https://github.com/digash) + +#### OpenCollective Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/babashka/contribute)] -#### Individuals +##### Individuals -#### Organizations +##### Organizations Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/babashka/contribute)] diff --git a/appveyor.yml b/appveyor.yml index 3b9a7c05..a84ac01b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,19 +16,20 @@ # - '%USERPROFILE%\.m2 -> project.clj' # - 'graalvm -> appveyor.yml' -# clone_script: -# - ps: >- -# if(-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { -# git clone -q --branch=$env:APPVEYOR_REPO_BRANCH https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER -# cd $env:APPVEYOR_BUILD_FOLDER -# git checkout -qf $env:APPVEYOR_REPO_COMMIT -# } else { -# git clone -q https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER -# cd $env:APPVEYOR_BUILD_FOLDER -# git fetch -q origin +refs/pull/$env:APPVEYOR_PULL_REQUEST_NUMBER/merge: -# git checkout -qf FETCH_HEAD -# } -# - cmd: git submodule update --init --recursive +clone_script: +- cmd: git config --global core.autocrlf true +- ps: >- + if(-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { + git clone -q --branch=$env:APPVEYOR_REPO_BRANCH https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER + cd $env:APPVEYOR_BUILD_FOLDER + git checkout -qf $env:APPVEYOR_REPO_COMMIT + } else { + git clone -q https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER + cd $env:APPVEYOR_BUILD_FOLDER + git fetch -q origin +refs/pull/$env:APPVEYOR_PULL_REQUEST_NUMBER/merge: + git checkout -qf FETCH_HEAD + } +- cmd: git submodule update --init --recursive # build_script: # - cmd: >- @@ -65,10 +66,14 @@ # bb release-artifact %zip% -# set BABASHKA_TEST_ENV=native + set BABASHKA_EDN= + + set BABASHKA_TEST_ENV=native # call script/test.bat -# artifacts: -# - path: babashka-*-windows-amd64.zip -# name: babashka + call script/run_lib_tests.bat + +artifacts: +- path: babashka-*-windows-amd64.zip + name: babashka diff --git a/deps.clj b/deps.clj index 520b6b05..985e5ca7 160000 --- a/deps.clj +++ b/deps.clj @@ -1 +1 @@ -Subproject commit 520b6b053b7bdfe46990ab82220a2d13f79f9772 +Subproject commit 985e5ca7f9cb123f86a0747aded1ee98e1f3deee diff --git a/deps.edn b/deps.edn index ec064874..275b302e 100644 --- a/deps.edn +++ b/deps.edn @@ -37,14 +37,17 @@ org.clojure/core.match {:mvn/version "1.0.0"} hiccup/hiccup {:mvn/version "2.0.0-alpha2"} rewrite-clj/rewrite-clj {:mvn/version "1.0.605-alpha"} - selmer/selmer {:mvn/version "1.12.40"}} + selmer/selmer {:mvn/version "1.12.40"} + com.taoensso/timbre {:mvn/version "5.1.2"} + org.clojure/tools.logging {:mvn/version "1.1.0"}} :aliases {:babashka/dev {:main-opts ["-m" "babashka.main"]} :profile {:extra-deps {com.clojure-goes-fast/clj-async-profiler {:mvn/version "0.4.1"}} :extra-paths ["test"] - :jvm-opts ["-Djdk.attach.allowAttachSelf"] + :jvm-opts ["-Djdk.attach.allowAttachSelf" + "-Dclojure.compiler.direct-linking=true"] :main-opts ["-m" "babashka.profile"]} :lib-tests {:extra-paths ["process/src" "process/test"] @@ -79,7 +82,11 @@ failjure/failjure {:mvn/version "2.1.1"} io.helins/binf {:mvn/version "1.1.0-beta0"} rm-hull/jasentaa {:mvn/version "0.2.5"} - slingshot/slingshot {:mvn/version "0.12.2"}} + slingshot/slingshot {:mvn/version "0.12.2"} + io.replikativ/hasch {:mvn/version "0.3.7"} + com.grammarly/omniconf {:mvn/version "0.4.3"} + crispin/crispin {:mvn/version "0.3.8"} + org.clojure/data.json {:mvn/version "2.4.0"}} :classpath-overrides {org.clojure/clojure nil org.clojure/spec.alpha nil org.clojure/core.specs.alpha nil}} diff --git a/doc/dev.md b/doc/dev.md index d9e4e15c..2450c386 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -7,7 +7,7 @@ You need [lein](https://leiningen.org/) for running JVM tests and/or producing u To work on Babashka itself make sure Git submodules are checked out. ``` shellsession -$ git clone https://github.com/borkdude/babashka --recursive +$ git clone https://github.com/babashka/babashka --recursive ``` To update later on: @@ -66,7 +66,7 @@ Findings from various experiments with JDBC drivers in babashka: 20MB to the binary. Since sqlite has a nice CLI we could also just shell out to it (there's an example in the examples dir). We could also build a `babashka.sqlite` namespace around the CLI maybe similar to - `babashka.curl`. See [#385](https://github.com/borkdude/babashka/issues/385) + `babashka.curl`. See [#385](https://github.com/babashka/babashka/issues/385) for details. - HSQLDB: easy to get going with Graalvm. Adds 10 MB to the binary. It's under a feature flag right now on master. See [build.md](build.md) for details. Derby @@ -75,7 +75,7 @@ Findings from various experiments with JDBC drivers in babashka: got it to crash. 4800m did work, but it took 17 minutes (compared to 10 minutes without this feature). - MySQL / MariaDB: can't get those to work yet. Work in progress in issue - [#387](https://github.com/borkdude/babashka/issues/387). + [#387](https://github.com/babashka/babashka/issues/387). To progress work on sqlite and mySQL, I need a working Clojure example. If you want to contribute, consider making a an example Clojure GraalVM CLI that puts diff --git a/doc/news.md b/doc/news.md index b6b7192b..f89e656c 100644 --- a/doc/news.md +++ b/doc/news.md @@ -5,15 +5,34 @@ you have anything to add. Also see [#babashka](https://twitter.com/hashtag/babashka?src=hashtag_click&f=live) on Twitter. +## 2021-06 + +- New babashka 0.4.4 - 0.4.5 released. +- Share your babashka creations on the [Show and tell](https://github.com/babashka/babashka/discussions/categories/show-and-tell) forum on Github. +- [Integrating Babashka into Bazel](https://timjaeger.io/20210627-integrating-babashka-with-bazel.html) by Tim Jäger +- [Babashka + scittle guestbook example](https://github.com/kloimhardt/babashka-scittle-guestbook) +- [Slingshot works with babashka](https://twitter.com/borkdude/status/1402547783295504387) +- [Spire gets a babashka pod](https://twitter.com/epic_castle/status/1402212817533431808) +- [Text to speech AWS example](https://twitter.com/FieryCodDev/status/1401843357555511301) with scittle and babashka. +- [Game of Life](https://gist.github.com/mmzsource/655b9dcfe56eed8a045022837186ed84) +- [ob-babashka](https://gist.github.com/adam-james-v/f4d2b75a70b095d14a351a1eff96b4b0): Emacs org-babel functions for babashka. +- [Normalize auto-resolved keywords](https://github.com/babashka/babashka/tree/master/examples#normalize-keywordsclj) +- [Create PostgreSQL backups](https://twitter.com/stelstuff/status/1400559261025980418) using babashka. +- [Change flutter SDK](https://gist.github.com/ampersanda/aac70cc0644df12199ea32988f3c4d73) using babashka. + ## 2021-05 -- Babashka 0.3.6-0.4.0 release. Highlights: +- Babashka 0.3.7 - 0.4.3 released. Highlights: - New [task runner feature](https://book.babashka.org/#tasks). - Add [Selmer](https://github.com/yogthos/Selmer) to built-in libraries. - Add compatibility with [jasentaa](https://github.com/rm-hull/jasentaa). - New [website](https://babashka.org). +- [Talk](https://youtu.be/Yjeh57eE9rg): Babashka: a native Clojure interpreter for scripting — The 2021 Graal Workshop at CGO +- [Blog](https://savo.rocks/posts/playing-new-music-on-old-car-stereo-with-clojure-and-babashka/): Playing New Music On Old Car Stereo With Clojure And Babashka - [Homoiconicity and feature flags](https://martinklepsch.org/posts/homoiconicity-and-feature-flags.html) by Martin Klepsch. +- [Manage your macOS setup](https://github.com/cldwalker/osx-setup) using babashka. - [Localizing a Ghost theme](https://martinklepsch.org/posts/localizing-a-ghost-theme.html) by Martin Klepsch. +- [Babashka SQL pods 0.0.8](https://twitter.com/borkdude/status/1396136828479188997) including a MySQL pod ## 2021-04 diff --git a/doc/projects.md b/doc/projects.md index 41737362..512d3e48 100644 --- a/doc/projects.md +++ b/doc/projects.md @@ -40,6 +40,10 @@ The following libraries and projects are known to work with babashka. - [rewrite-edn](#rewrite-edn) - [expound](#expound) - [omniconf](#omniconf) + - [slingshot](#slingshot) + - [hasch](#hasch) + - [crispin](#crispin) + - [ffclj](#ffclj) - [Pods](#pods) - [Projects](#projects-1) - [babashka-test-action](#babashka-test-action) @@ -584,6 +588,51 @@ NOTE: slingshot's tests pass with babashka except one: catching a record types by name. This is due to a difference in how records are implemented in babashka. This may be fixed later if this turns out to be really useful. +### [hasch](https://github.com/replikativ/hasch) + +Cross-platform (JVM and JS atm.) edn data structure hashing for Clojure. + +``` clojure +$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {io.replikativ/hasch {:mvn/version "0.3.7"}}}') +$ bb -e "(use 'hasch.core) (edn-hash (range 100))" +(168 252 48 247 180 148 51 182 108 76 20 251 155 187 66 8 124 123 103 28 250 151 26 139 10 216 119 168 101 123 130 225 66 168 48 63 53 99 25 117 173 29 198 229 101 196 162 30 23 145 7 166 232 193 57 239 226 238 240 41 254 78 135 122) +``` + +NOTE: hasch's tests pass with babashka except the test around hashing +records. This is due to a difference in how records are implemented in +babashka. This may be fixed later if this turns out to be really useful. + +### [crispin](https://github.com/dunaj-project/crispin) + +Populate a configuration map from multiple sources (environment variables, +system variables, config files, etc.) + +Example: + +script.clj +``` clojure +#!/usr/bin/env bb + +(ns script + (:require [babashka.deps :as deps])) + +(deps/add-deps + '{:deps {crispin/crispin {:mvn/version "0.3.8"}}}) + +(require '[crispin.core :as crispin]) +(def app-cfg (crispin/cfg)) +(app-cfg :foo) +``` + +``` text +FOO=1 script.clj +"1" +``` + +### [ffclj](https://github.com/luissantos/ffclj) + +A wrapper around executing `ffmpeg` and `ffprobe`. Supports progress reporting via core.async channels. + ## Pods [Babashka pods](https://github.com/babashka/babashka.pods) are programs that can diff --git a/examples/README.md b/examples/README.md index 37553d7a..e02d648a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -25,13 +25,17 @@ - [Invoke vim inside a script](#invoke-vim-inside-a-script) - [Portal](#portal) - [Image viewer](#image-viewer) - - [File server](#file-server) + - [HTTP server](#http-server) - [Torrent viewer](#torrent-viewer) - [cprop.clj](#cpropclj) - [fzf](#fzf) - [digitalocean-ping.clj](#digitalocean-pingclj) - [download-aliases.clj](#download-aliasesclj) - [Is TTY?](#is-tty) + - [normalize-keywords.clj](#normalize-keywordsclj) + - [Check stdin for data](#check-stdin-for-data) + - [Using org.clojure/data.xml](#using-orgclojuredataxml) + - [Simple logger](#simple-logger) Here's a gallery of useful examples. Do you have a useful example? PR welcome! @@ -372,14 +376,15 @@ $ examples/image-viewer.clj See [image-viewer.clj](image-viewer.clj). -## File server +## HTTP Server -Opens browser window and lets user navigate through filesystem. +Opens browser window and lets user navigate through filesystem, similar to +`python3 -m http.server`. Example usage: ``` shell -$ examples/file-server.clj +$ examples/http-server.clj ``` See [file-server.clj](file-server.clj). @@ -464,3 +469,56 @@ STDIN is TTY?: true STDOUT is TTY?: true STDERR is TTY?: false ``` + +## [normalize-keywords.clj](normalize-keywords.clj) + +Provide a Clojure file to the script and it will print the Clojure file with +auto-resolved keywords normalized to fully qualified ones without double colons: +`::set/foo` becomes `:clojure.set/foo`. + +``` clojure +$ cat /tmp/test.clj +(ns test (:require [clojure.set :as set])) + +[::set/foo ::bar] + +$ bb examples/normalize-keywords.clj /tmp/test.clj +(ns test (:require [clojure.set :as set])) + +[:clojure.set/foo :test/bar] +``` + +## Check stdin for data + +```shell +# when piping something in, we get a positive number +$ echo 'abc' | bb '(pos? (.available System/in))' +true +# even if we echo an empty string, we still get the newline +$ echo '' | bb '(pos? (.available System/in))' +true +# with nothing passed in, we finally return false +$ bb '(pos? (.available System/in))' +false +``` + +## Using org.clojure/data.xml + +[xml-example.clj](xml-example.clj) explores some of the capabilities provided +by the `org.clojure/data.xml` library (required as `xml` by default in Babashka). +While running the script will show some output, reading the file shows the library +in use. + +```shell +$ bb examples/xml-example.clj +... some vaguely interesting XML manipulation output +``` + +## Simple logger + +[logger.clj](logger.clj) is a simple logger that works in bb. + +``` clojure +$ bb "(require 'logger) (logger/log \"the logger says hi\")" +:1:19 the logger says hi +``` diff --git a/examples/file-server.clj b/examples/http-server.clj similarity index 99% rename from examples/file-server.clj rename to examples/http-server.clj index d8fa3f72..9a47f63d 100755 --- a/examples/file-server.clj +++ b/examples/http-server.clj @@ -3,7 +3,7 @@ ;; Source: https://gist.github.com/holyjak/36c6284c047ffb7573e8a34399de27d8 ;; Based on https://github.com/babashka/babashka/blob/master/examples/image_viewer.clj -(ns file-server +(ns http-server (:require [babashka.fs :as fs] [clojure.java.browse :as browse] [clojure.string :as str] diff --git a/examples/logger.clj b/examples/logger.clj new file mode 100644 index 00000000..bc2c48ad --- /dev/null +++ b/examples/logger.clj @@ -0,0 +1,11 @@ +(ns logger) + +(defmacro log [& msgs] + (let [m (meta &form) + _ns (ns-name *ns*) ;; can also be used for logging + file *file*] + `(binding [*out* *err*] ;; or bind to (io/writer log-file) + (println (str ~file ":" + ~(:line m) ":" + ~(:column m)) + ~@msgs)))) diff --git a/examples/normalize-keywords.clj b/examples/normalize-keywords.clj new file mode 100644 index 00000000..b4b455c7 --- /dev/null +++ b/examples/normalize-keywords.clj @@ -0,0 +1,38 @@ +(ns normalize-keywords + (:require [babashka.pods :as pods] + [rewrite-clj.node :as node] + [rewrite-clj.zip :as z])) + +(pods/load-pod 'borkdude/clj-kondo "2021.06.18") + +(require '[pod.borkdude.clj-kondo :as clj-kondo]) + +(def code (first *command-line-args*)) + +(def findings + (->> (with-in-str code + (clj-kondo/run! {:lint [code] + :config {:output {:analysis {:keywords true}}}})) + :analysis + :keywords + (filter (some-fn :alias :auto-resolved)))) + +(defn finding->keyword [{:keys [:ns :name]}] + (keyword (str ns) (str name))) + +(defn remove-locs [zloc findings] + (loop [zloc zloc + findings (seq findings)] + (if findings + (let [{:keys [:row :col] :as finding} (first findings) + node (z/node zloc) + m (meta node)] + (if (and (= row (:row m)) + (= col (:col m))) + (let [k (finding->keyword finding) + zloc (z/replace zloc (node/coerce k))] + (recur zloc (next findings))) + (recur (z/next zloc) findings))) + (println (str (z/root zloc)))))) + +(remove-locs (z/of-file code) findings) diff --git a/examples/xml-example.clj b/examples/xml-example.clj new file mode 100644 index 00000000..8aa28dbd --- /dev/null +++ b/examples/xml-example.clj @@ -0,0 +1,78 @@ +; let's build up a little data structure to play with + +(def pet-store-sexp + [:pet-store + [:family + [:owners + [:name "Terry Smith"] + [:name "Sam Smith"] + [:phone "555-1212"]] + [:animals + [:animal {:type "dog"} "Sparky"]]] + [:family + [:owners + [:name "Pat Jones"] + [:phone "555-2121"]] + [:animals + [:animal {:type "hamster"} "Oliver"] + [:animal {:type "cat"} "Kat"]]]]) + +; we can build XML from this + +(def xml-str (xml/indent-str (xml/sexp-as-element pet-store-sexp))) + +(println "Our XML as a string is:") +(println xml-str) + +(comment xml-str is + " + + + + Terry Smith...") + +; and then we can parse that XML back into a data structure + +(def xml-tree (xml/parse-str xml-str)) + +#_"xml-tree is a nested associative structure: + {:tag :pet-store, + :attrs {}, + :content + ({:tag :family, + :attrs {}, + :content ...})}" + + +; with a couple of quick helper functions... + +(defn get-by-tag + "takes a seq of XML elements (or a 'root-ish' element) and a tag, filters by tag name, and gets the content of each" + [elems tag-name] + ; if we get (presumably) a root element, wrap it in a vector so we can still + ; filter by its tag + (if (xml/element? elems) + (recur [elems] tag-name) + (->> (filter #(= (:tag %) tag-name) elems) + (mapcat :content)))) + +(defn get-in-by-tag + "takes a seq of XML elements and a vector of tags, and drills into each + element by the tags, sort of like a mash-up of core/get-in and an XPath + lookup" + [elems tag-vec] + (reduce get-by-tag elems tag-vec)) + +; we can do things like... + +(println "all the owner names:" (get-in-by-tag + xml-tree + [:pet-store :family :owners :name])) + +(println "all the animal names:" (get-in-by-tag + xml-tree + [:pet-store :family :animals :animal])) + +(println "all the phone numbers:" (get-in-by-tag + xml-tree + [:pet-store :family :owners :phone])) diff --git a/project.clj b/project.clj index 86cf3710..2b2c2787 100644 --- a/project.clj +++ b/project.clj @@ -14,7 +14,8 @@ ;; :java-source-paths ["sci/reflector/src-java"] :java-source-paths ["src-java"] :resource-paths ["resources" "sci/resources"] - :test-selectors {:windows :windows} + :test-selectors {:default (complement :windows-only) + :windows (complement :skip-windows)} :dependencies [[org.clojure/clojure "1.11.0-alpha1"] [borkdude/edamame "0.0.11"] [borkdude/graal.locking "0.0.2"] @@ -22,7 +23,9 @@ [cheshire "5.10.0"] [nrepl/bencode "1.1.0"] [borkdude/sci.impl.reflector "0.0.1"] - [org.clojure/test.check "1.1.0"]] + [org.clojure/test.check "1.1.0"] + [com.taoensso/timbre "5.1.2"] + [org.clojure/tools.logging "1.1.0"]] :profiles {:feature/xml {:source-paths ["feature-xml"] :dependencies [[org.clojure/data.xml "0.2.0-alpha6"]]} :feature/yaml {:source-paths ["feature-yaml"] diff --git a/sci b/sci index 79376504..edff738f 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 7937650453b7ba9eb9ee43ea30b333b5950dc21e +Subproject commit edff738f0c2eeb8959292882c3be64d0155e2f84 diff --git a/script/lib_tests/run_all_libtests.bat b/script/lib_tests/run_all_libtests.bat new file mode 100644 index 00000000..015073a1 --- /dev/null +++ b/script/lib_tests/run_all_libtests.bat @@ -0,0 +1,5 @@ +if "%BABASHKA_TEST_ENV%" EQU "native" (set BB_CMD=.\bb) else (set BB_CMD=lein bb) + +for /f %%i in ('.\bb clojure -A:lib-tests -Spath') do set BABASHKA_CLASSPATH=%%i + +%BB_CMD% -cp "%BABASHKA_CLASSPATH%;test-resources/lib_tests" -f test-resources/lib_tests/babashka/run_all_libtests.clj %* diff --git a/script/run_lib_tests.bat b/script/run_lib_tests.bat new file mode 100644 index 00000000..003786b9 --- /dev/null +++ b/script/run_lib_tests.bat @@ -0,0 +1 @@ +call script/lib_tests/run_all_libtests.bat %* || exit /B 1 diff --git a/script/test.bat b/script/test.bat index ba69e4ec..d0fe976e 100755 --- a/script/test.bat +++ b/script/test.bat @@ -8,4 +8,41 @@ echo "BABASHKA_TEST_ENV: %BABASHKA_TEST_ENV%" set JAVA_HOME=%GRAALVM_HOME% set PATH=%GRAALVM_HOME%\bin;%PATH% -call lein do clean, test :windows +set BABASHKA_PRELOADS= +set BABASHKA_CLASSPATH= +set BABASHKA_PRELOADS_TEST= +set BABASHKA_CLASSPATH_TEST= +set BABASHKA_POD_TEST= +set BABASHKA_SOCKET_REPL_TEST= + +echo "running tests part 1" +call lein do clean, test :windows || exit /B 1 + +set BABASHKA_PRELOADS=(defn __bb__foo [] "foo") (defn __bb__bar [] "bar") +set BABASHKA_PRELOADS_TEST=true +echo "running tests part 2" +call lein test :only babashka.main-test/preloads-test || exit /B 1 + +set BABASHKA_PRELOADS=(defn ithrow [] (/ 1 0)) +set BABASHKA_PRELOADS_TEST=true +echo "running tests part 3" +call lein test :only babashka.main-test/preloads-file-location-test || exit /B 1 + +set BABASHKA_PRELOADS=(require '[env-ns]) +set BABASHKA_CLASSPATH_TEST=true +set BABASHKA_CLASSPATH=test-resources/babashka/src_for_classpath_test/env +echo "running tests part 4" +call lein test :only babashka.classpath-test/classpath-env-test || exit /B 1 + +set BABASHKA_POD_TEST=true +call lein test :only babashka.pod-test || exit /B 1 + +set BABASHKA_SOCKET_REPL_TEST=true +call lein test :only babashka.impl.socket-repl-test || exit /B 1 + +set BABASHKA_PRELOADS= +set BABASHKA_CLASSPATH= +set BABASHKA_PRELOADS_TEST= +set BABASHKA_CLASSPATH_TEST= +set BABASHKA_POD_TEST= +set BABASHKA_SOCKET_REPL_TEST= diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index fae96345..38fa9cfe 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -100,21 +100,25 @@ java.io.InputStream java.io.IOException java.io.OutputStream + java.io.FileInputStream java.io.FileReader java.io.InputStreamReader java.io.OutputStreamWriter java.io.PrintStream java.io.PushbackInputStream + java.io.PushbackReader java.io.Reader java.io.SequenceInputStream java.io.StringReader java.io.StringWriter java.io.Writer + java.lang.Appendable java.lang.ArithmeticException java.lang.AssertionError java.lang.Boolean java.lang.Byte java.lang.Character + java.lang.CharSequence java.lang.Class java.lang.ClassNotFoundException java.lang.Comparable @@ -198,6 +202,7 @@ java.security.MessageDigest java.security.DigestInputStream java.security.SecureRandom + java.sql.Date java.text.ParseException ~@(when features/java-time? `[java.time.format.DateTimeFormatter @@ -273,8 +278,7 @@ clojure.lang.MapEntry clojure.lang.LineNumberingPushbackReader java.io.EOFException - java.io.PrintWriter - java.io.PushbackReader] + java.io.PrintWriter] :methods [borkdude.graal.LockFix] ;; support for locking :fields [clojure.lang.PersistentQueue] @@ -328,6 +332,9 @@ clojure.lang.Sequential clojure.lang.Seqable clojure.lang.Volatile + java.util.concurrent.atomic.AtomicInteger + java.util.concurrent.atomic.AtomicLong + java.util.Collection java.util.List java.util.Iterator java.util.Map$Entry] diff --git a/src/babashka/impl/clojure/main.clj b/src/babashka/impl/clojure/main.clj index 1e8ac7a4..aea9726e 100644 --- a/src/babashka/impl/clojure/main.clj +++ b/src/babashka/impl/clojure/main.clj @@ -17,6 +17,8 @@ babashka.impl.clojure.main (:refer-clojure :exclude [with-bindings])) +(set! *warn-on-reflection* true) + (defn demunge "Given a string representation of a fn class, as in a stack trace element, returns a readable version." diff --git a/src/babashka/impl/deps.clj b/src/babashka/impl/deps.clj index b5aee8d7..8e04c2a2 100644 --- a/src/babashka/impl/deps.clj +++ b/src/babashka/impl/deps.clj @@ -56,7 +56,7 @@ keywords) which will used to calculate classpath. The classpath is then used to resolve dependencies in babashka." ([deps-map] (add-deps deps-map nil)) - ([deps-map {:keys [:aliases]}] + ([deps-map {:keys [:aliases :env :extra-env :force]}] (when-let [paths (:paths deps-map)] (cp/add-classpath (str/join cp/path-sep paths))) (when-let [deps-map (not-empty (dissoc deps-map :paths :tasks :raw :min-bb-version))] @@ -65,12 +65,14 @@ :classpath-overrides {org.clojure/clojure "" org.clojure/spec.alpha "" org.clojure/core.specs.alpha ""}}) - args ["-Srepro" ;; do not include deps.edn from user config - "-Spath" "-Sdeps" (str deps-map) - "-Sdeps-file" "" ;; we reset deps file so the local deps.edn isn't used - ,] - args (conj args (str "-A:" (str/join ":" (cons ":org.babashka/defaults" aliases)))) - cp (with-out-str (apply deps/-main args)) + args (list "-Srepro" ;; do not include deps.edn from user config + "-Spath" "-Sdeps" (str deps-map) + "-Sdeps-file" "") ;; we reset deps file so the local deps.edn isn't used + args (if force (cons "-Sforce" args) args) + args (concat args [(str "-A:" (str/join ":" (cons ":org.babashka/defaults" aliases)))]) + cp (with-out-str (binding [deps/*env* env + deps/*extra-env* extra-env] + (apply deps/-main args))) cp (str/trim cp) cp (str/replace cp (re-pattern (str cp/path-sep "+$")) "")] (cp/add-classpath cp))))) @@ -109,6 +111,9 @@ (binding [*in* @sci/in *out* @sci/out *err* @sci/err + deps/*dir* (:dir opts) + deps/*env* (:env opts) + deps/*extra-env* (:extra-env opts) deps/*process-fn* (fn ([cmd] (p/process cmd opts)) ([cmd _] (p/process cmd opts))) diff --git a/src/babashka/impl/pprint.clj b/src/babashka/impl/pprint.clj index 073c2f7c..d1420cc4 100644 --- a/src/babashka/impl/pprint.clj +++ b/src/babashka/impl/pprint.clj @@ -1,10 +1,11 @@ (ns babashka.impl.pprint {:no-doc true} (:require [clojure.pprint :as pprint] - [sci.core :as sci] - [sci.impl.vars :as vars])) + [sci.core :as sci])) -(defonce patch-option-table +(defonce patched? (volatile! false)) + +(when-not @patched? (alter-var-root #'pprint/write-option-table (fn [m] (zipmap (keys m) @@ -17,12 +18,21 @@ #(when-let [v (get t (key %))] [v (val %)]) m)))) -(alter-var-root #'pprint/table-ize (constantly new-table-ize)) +(when-not @patched? + (alter-var-root #'pprint/table-ize (constantly new-table-ize)) + (alter-meta! #'pprint/write-option-table dissoc :private) + (alter-meta! #'pprint/with-pretty-writer dissoc :private) + (alter-meta! #'pprint/pretty-writer? dissoc :private) + (alter-meta! #'pprint/make-pretty-writer dissoc :private) + (alter-meta! #'pprint/execute-format dissoc :private)) -(alter-meta! #'pprint/write-option-table dissoc :private) -(alter-meta! #'pprint/with-pretty-writer dissoc :private) -(alter-meta! #'pprint/pretty-writer? dissoc :private) -(alter-meta! #'pprint/make-pretty-writer dissoc :private) +(def pprint-ns (sci/create-ns 'clojure.pprint nil)) + +(def print-right-margin + (sci/new-dynamic-var '*print-right-margin* pprint/*print-right-margin* {:ns pprint-ns})) + +(def print-pprint-dispatch + (sci/new-dynamic-var '*print-pprint-dispatch* pprint/*print-pprint-dispatch* {:ns pprint-ns})) (def new-write (fn [object & kw-args] @@ -46,12 +56,8 @@ (if (nil? optval) (.toString ^java.io.StringWriter base-writer)))))))) -(alter-var-root #'pprint/write (constantly new-write)) - -(def pprint-ns (vars/->SciNamespace 'clojure.pprint nil)) - -(def print-right-margin - (sci/new-dynamic-var 'print-right-margin pprint/*print-right-margin* {:ns pprint-ns})) +(when-not @patched? + (alter-var-root #'pprint/write (constantly new-write))) (defn print-table "Prints a collection of maps in a textual table. Prints table headings @@ -62,22 +68,80 @@ (binding [*out* @sci/out] (pprint/print-table ks rows)))) +(defmacro formatter-out + "Makes a function which can directly run format-in. The function is + fn [& args] ... and returns nil. This version of the formatter macro is + designed to be used with *out* set to an appropriate Writer. In particular, + this is meant to be used as part of a pretty printer dispatch method. + format-in can be either a control string or a previously compiled format." + {:added "1.2"} + [format-in] + `(let [format-in# ~format-in + cf# (if (string? format-in#) (#'clojure.pprint/cached-compile format-in#) format-in#)] + (fn [& args#] + (let [navigator# (#'clojure.pprint/init-navigator args#)] + (#'clojure.pprint/execute-format cf# navigator#))))) + (defn pprint "Pretty print object to the optional output writer. If the writer is not provided, print the object to the currently bound value of *out*." ([s] (pprint s @sci/out)) ([s writer] - (binding [pprint/*print-right-margin* @print-right-margin] + (binding [pprint/*print-right-margin* @print-right-margin + pprint/*print-pprint-dispatch* @print-pprint-dispatch] (pprint/pprint s writer)))) +(defn cl-format + "An implementation of a Common Lisp compatible format function. cl-format formats its +arguments to an output stream or string based on the format control string given. It +supports sophisticated formatting of structured data. +Writer is an instance of java.io.Writer, true to output to *out* or nil to output +to a string, format-in is the format control string and the remaining arguments +are the data to be formatted. +The format control string is a string to be output with embedded 'format directives' +describing how to format the various arguments passed in. +If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format +returns nil. +For example: + (let [results [46 38 22]] + (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\" + (count results) results)) +Prints to *out*: + There are 3 results: 46, 38, 22 +Detailed documentation on format control strings is available in the \"Common Lisp the +Language, 2nd edition\", Chapter 22 (available online at: +http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000) +and in the Common Lisp HyperSpec at +http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm +" + [& args] + ;; bind *out* to sci/out, so with-out-str works + (binding [*out* @sci/out] + (apply pprint/cl-format args))) + +(defn execute-format + "We need to bind sci/out to *out* so all calls to clojure.core/print are directed + to the writer bound to *out* by the cl-format logic." + [& args] + (sci/binding [sci/out *out*] + (apply #'pprint/execute-format args))) + (def pprint-namespace {'pp (sci/copy-var pprint/pp pprint-ns) 'pprint (sci/copy-var pprint pprint-ns) 'print-table (sci/copy-var print-table pprint-ns) '*print-right-margin* print-right-margin - 'cl-format (sci/copy-var pprint/cl-format pprint-ns) + 'cl-format (sci/copy-var cl-format pprint-ns) ;; we alter-var-root-ed write above, so this should copy the right function 'write (sci/copy-var pprint/write pprint-ns) 'simple-dispatch (sci/copy-var pprint/simple-dispatch pprint-ns) + 'formatter-out (sci/copy-var formatter-out pprint-ns) + 'cached-compile (sci/copy-var pprint/cached-compile pprint-ns) #_(sci/new-var 'cache-compile @#'pprint/cached-compile (meta @#'pprint/cached-compile)) + 'init-navigator (sci/copy-var pprint/init-navigator pprint-ns) + 'execute-format (sci/copy-var execute-format pprint-ns) + 'with-pprint-dispatch (sci/copy-var pprint/with-pprint-dispatch pprint-ns) + '*print-pprint-dispatch* print-pprint-dispatch }) + +(vreset! patched? true) diff --git a/src/babashka/impl/repl.clj b/src/babashka/impl/repl.clj index 73b2267f..9cbf4d95 100644 --- a/src/babashka/impl/repl.clj +++ b/src/babashka/impl/repl.clj @@ -38,11 +38,18 @@ (str ":" line ":" column))"]")))) (sio/flush)))) +(defn skip-if-eol + "Inspired by skip-if-eol from clojure.main." + [s] + (let [c (r/read-char s)] + (when-not (= c \newline) + (r/unread s c)))) + (defn repl "REPL with predefined hooks for attachable socket server." ([sci-ctx] (repl sci-ctx nil)) ([sci-ctx {:keys [:init :read :eval :need-prompt :prompt :flush :print :caught]}] - (let [in (r/indexing-push-back-reader (r/push-back-reader @sci/in))] + (let [in @sci/in] (m/repl :init (or init (fn [] @@ -57,6 +64,7 @@ :read (or read (fn [_request-prompt request-exit] (let [v (parser/parse-next sci-ctx in)] + (skip-if-eol in) (if (or (identical? :repl/quit v) (identical? :repl/exit v) (identical? parser/eof v)) diff --git a/src/babashka/impl/timbre.clj b/src/babashka/impl/timbre.clj new file mode 100644 index 00000000..803cf80b --- /dev/null +++ b/src/babashka/impl/timbre.clj @@ -0,0 +1,137 @@ +(ns babashka.impl.timbre + (:require [clojure.tools.logging] + [clojure.tools.logging.impl :as impl] + [sci.core :as sci] + [taoensso.encore :as enc :refer [have]] + [taoensso.timbre :as timbre])) + +;;;; timbre + +(def tns (sci/create-ns 'taoensso.timbre nil)) + +(defn- fline [and-form] (:line (meta and-form))) + +(defmacro log! ; Public wrapper around `-log!` + "Core low-level log macro. Useful for tooling, etc. + * `level` - must eval to a valid logging level + * `msg-type` - must eval to e/o #{:p :f nil} + * `opts` - ks e/o #{:config :?err :?ns-str :?file :?line :?base-data :spying?} + Supports compile-time elision when compile-time const vals + provided for `level` and/or `?ns-str`." + [level msg-type args & [opts]] + (have [:or nil? sequential?] args) ; To allow -> (delay [~@args]) + (let [{:keys [?ns-str] :or {?ns-str (str @sci/ns)}} opts] + ;; level, ns may/not be compile-time consts: + (when-not (timbre/-elide? level ?ns-str) + (let [{:keys [config ?err ?file ?line ?base-data spying?] + :or {config 'taoensso.timbre/*config* + ?err :auto ; => Extract as err-type v0 + ?file @sci/file + ;; NB waiting on CLJ-865: + ?line (fline &form)}} opts + + ?file (when (not= ?file "NO_SOURCE_PATH") ?file) + + ;; Identifies this particular macro expansion; note that this'll + ;; be fixed for any fns wrapping `log!` (notably `tools.logging`, + ;; `slf4j-timbre`, etc.): + callsite-id + (hash [level msg-type args ; Unevaluated args (arg forms) + ?ns-str ?file ?line (rand)])] + + `(taoensso.timbre/-log! ~config ~level ~?ns-str ~?file ~?line ~msg-type ~?err + (delay [~@args]) ~?base-data ~callsite-id ~spying?))))) + +(defn make-ns [ns sci-ns ks] + (reduce (fn [ns-map [var-name var]] + (let [m (meta var) + no-doc (:no-doc m) + doc (:doc m) + arglists (:arglists m)] + (if no-doc ns-map + (assoc ns-map var-name + (sci/new-var (symbol var-name) @var + (cond-> {:ns sci-ns + :name (:name m)} + (:macro m) (assoc :macro true) + doc (assoc :doc doc) + arglists (assoc :arglists arglists))))))) + {} + (select-keys (ns-publics ns) ks))) + +(def config (sci/new-dynamic-var '*config* timbre/*config* {:ns tns})) + +(defn swap-config! [f & args] + (apply sci/alter-var-root config f args)) + +(defn set-level! [level] (swap-config! (fn [m] (assoc m :min-level level)))) + +(def timbre-namespace + (assoc (make-ns 'taoensso.timbre tns ['trace 'tracef 'debug 'debugf + 'info 'infof 'warn 'warnf + 'error 'errorf + '-log! 'with-level + 'println-appender 'spit-appender]) + 'log! (sci/copy-var log! tns) + '*config* config + 'swap-config! (sci/copy-var swap-config! tns) + 'set-level! (sci/copy-var set-level! tns))) + +;;;; clojure.tools.logging + +(defn- force-var "To support dynamic vars, etc." + [x] (if (instance? clojure.lang.IDeref x) (deref x) x)) + +(deftype Logger [logger-ns-str timbre-config] + clojure.tools.logging.impl/Logger + + (enabled? [_ level] + ;; No support for per-call config + (timbre/may-log? level logger-ns-str + (force-var timbre-config))) + + (write! [_ level throwable message] + (log! level :p + [message] ; No support for pre-msg raw args + {:config (force-var timbre-config) ; No support for per-call config + :?ns-str logger-ns-str + :?file nil ; No support + :?line nil ; '' + :?err throwable}))) + +(deftype LoggerFactory [get-logger-fn] + clojure.tools.logging.impl/LoggerFactory + (name [_] "Timbre") + (get-logger [_ logger-ns] (get-logger-fn logger-ns))) + +(alter-var-root + #'clojure.tools.logging/*logger-factory* + (fn [_] + (LoggerFactory. + (enc/memoize (fn [logger-ns] (Logger. (str logger-ns) config)))))) + +(def lns (sci/create-ns 'clojure.tools.logging nil)) + +(defmacro log + "Evaluates and logs a message only if the specified level is enabled. See log* + for more details." + ([level message] + `(clojure.tools.logging/log ~level nil ~message)) + ([level throwable message] + `(clojure.tools.logging/log ~(deref sci/ns) ~level ~throwable ~message)) + ([logger-ns level throwable message] + `(clojure.tools.logging/log clojure.tools.logging/*logger-factory* ~logger-ns ~level ~throwable ~message)) + ([logger-factory logger-ns level throwable message] + `(let [logger# (impl/get-logger ~logger-factory ~logger-ns)] + (if (impl/enabled? logger# ~level) + (clojure.tools.logging/log* logger# ~level ~throwable ~message))))) + +(def tools-logging-namespace + (assoc (make-ns 'clojure.tools.logging lns ['debug 'debugf 'info 'infof 'warn 'warnf 'error 'errorf + 'logp 'logf '*logger-factory* 'log*]) + 'log (sci/copy-var log lns))) + +(def lins (sci/create-ns 'clojure.tools.logging.impl nil)) + +(def tools-logging-impl-namespace + (make-ns 'clojure.tools.logging.impl lins ['get-logger 'enabled?])) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 7a8c1725..b7811121 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -33,6 +33,8 @@ [babashka.impl.socket-repl :as socket-repl] [babashka.impl.tasks :as tasks :refer [tasks-namespace]] [babashka.impl.test :as t] + [babashka.impl.timbre :refer [timbre-namespace tools-logging-namespace + tools-logging-impl-namespace]] [babashka.impl.tools.cli :refer [tools-cli-namespace]] [babashka.nrepl.server :as nrepl-server] [babashka.wait :as wait] @@ -111,14 +113,20 @@ (defn print-help [_ctx _command-line-args] (println (str "Babashka v" version)) (println " -Usage: bb [global-opts] [eval opts] [cmdline args] -or: bb [global-opts] file [cmdline args] -or: bb [global-opts] subcommand [subcommand opts] [cmdline args] +Usage: bb [svm-opts] [global-opts] [eval opts] [cmdline args] +or: bb [svm-opts] [global-opts] file [cmdline args] +or: bb [svm-opts] [global-opts] subcommand [subcommand opts] [cmdline args] + +Substrate VM opts: + + -Xmx[g|G|m|M|k|K] Set a maximum heap size (e.g. -Xmx256M to limit the heap to 256MB). + -XX:PrintFlags= Print all Substrate VM options. Global opts: - -cp, --classpath Classpath to use. Overrides bb.edn classpath. - --debug Print debug information and internal stacktrace in case of exception. + -cp, --classpath Classpath to use. Overrides bb.edn classpath. + --debug Print debug information and internal stacktrace in case of exception. + --force Passes -Sforce to deps.clj, forcing recalculation of the classpath. Help: @@ -314,7 +322,7 @@ Use bb run --help to show this help output. (def namespaces (cond-> {'user {'*input* (ctx-fn - (fn [_ctx] + (fn [_ctx _bindings] (force @input-var)) nil)} 'clojure.tools.cli tools-cli-namespace @@ -345,7 +353,10 @@ Use bb run --help to show this help output. 'babashka.process process-namespace 'clojure.core.server clojure-core-server 'babashka.deps deps-namespace - 'babashka.tasks tasks-namespace} + 'babashka.tasks tasks-namespace + 'taoensso.timbre timbre-namespace + 'clojure.tools.logging tools-logging-namespace + 'clojure.tools.logging.impl tools-logging-impl-namespace} features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace)) features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace) 'flatland.ordered.map @(resolve 'babashka.impl.ordered/ordered-map-ns)) @@ -404,13 +415,15 @@ Use bb run --help to show this help output. @(resolve 'babashka.impl.selmer/selmer-validator-namespace)))) (def imports - '{ArithmeticException java.lang.ArithmeticException + '{Appendable java.lang.Appendable + ArithmeticException java.lang.ArithmeticException AssertionError java.lang.AssertionError BigDecimal java.math.BigDecimal BigInteger java.math.BigInteger Boolean java.lang.Boolean Byte java.lang.Byte Character java.lang.Character + CharSequence java.lang.CharSequence Class java.lang.Class ClassNotFoundException java.lang.ClassNotFoundException Comparable java.lang.Comparable @@ -493,9 +506,13 @@ Use bb run --help to show this help output. ("--doc") {:doc true :command-line-args (rest options)} + ;; renamed to --debug ("--verbose") (recur (next options) (assoc opts-map :verbose? true)) + ("--force") (recur (next options) + (assoc opts-map + :force? true)) ("--describe") (recur (next options) (assoc opts-map :describe? true)) @@ -663,7 +680,7 @@ Use bb run --help to show this help output. :help :file :command-line-args :expressions :stream? :repl :socket-repl :nrepl - :debug :classpath + :debug :classpath :force? :main :uberscript :describe? :jar :uberjar :clojure :doc :run :list-tasks]} @@ -691,7 +708,7 @@ Use bb run --help to show this help output. _ (if classpath (cp/add-classpath classpath) ;; when classpath isn't set, we calculate it from bb.edn, if present - (when-let [bb-edn @common/bb-edn] (deps/add-deps bb-edn))) + (when-let [bb-edn @common/bb-edn] (deps/add-deps bb-edn {:force force?}))) abs-path (when file (let [abs-path (.getAbsolutePath (io/file file))] (vars/bindRoot sci/file abs-path) diff --git a/test-resources/clojure-dir-test/deps.edn b/test-resources/clojure-dir-test/deps.edn new file mode 100644 index 00000000..b489dfa7 --- /dev/null +++ b/test-resources/clojure-dir-test/deps.edn @@ -0,0 +1 @@ +{:deps {medley/medley {:mvn/version "1.3.0"}}} diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index e7dd18d3..a9b11e6d 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -1,5 +1,6 @@ (ns babashka.run-all-libtests (:require [clojure.java.io :as io] + [clojure.string :as str] [clojure.test :as t])) (def ns-args (set (map symbol *command-line-args*))) @@ -19,6 +20,10 @@ (swap! status (fn [status] (merge-with + status (dissoc m :type)))))))) +(def windows? (-> (System/getProperty "os.name") + (str/lower-case) + (str/includes? "win"))) + ;;;; clj-http-lite (test-namespaces 'clj-http.lite.client-test) @@ -38,8 +43,8 @@ (prn (random-uuid)) ;;;; babashka.curl - -(test-namespaces 'babashka.curl-test) +; skip tests on Windows because of the :compressed thing +(when-not windows? (test-namespaces 'babashka.curl-test)) ;;;; cprop @@ -114,7 +119,8 @@ (require '[babashka.curl :as curl]) (spit "deps_test.clj" - (:body (curl/get "https://raw.githubusercontent.com/borkdude/deps.clj/master/deps.clj"))) + (:body (curl/get "https://raw.githubusercontent.com/borkdude/deps.clj/master/deps.clj" + (if windows? {:compressed false} {})))) (binding [*command-line-args* ["-Sdescribe"]] (load-file "deps_test.clj")) @@ -170,13 +176,13 @@ (test-namespaces 'httpkit.client-test) ;;;; babashka.process +(when-not windows? + ;; test built-in babashka.process + (test-namespaces 'babashka.process-test) -;; test built-in babashka.process -(test-namespaces 'babashka.process-test) - -;; test babashka.process from source -(require '[babashka.process] :reload) -(test-namespaces 'babashka.process-test) + ;; test babashka.process from source + (require '[babashka.process] :reload) + (test-namespaces 'babashka.process-test)) (test-namespaces 'core-match.core-tests) @@ -218,6 +224,16 @@ ;; 'slingshot.test-test ) +(test-namespaces 'hasch.test + ) + +(test-namespaces 'omniconf.core-test) + +(test-namespaces 'crispin.core-test) + +(test-namespaces 'clojure.data.json-test + 'clojure.data.json-test-suite-test) + ;;;; final exit code (let [{:keys [:test :fail :error] :as m} @status] diff --git a/test-resources/lib_tests/clojure/data/json_test.clj b/test-resources/lib_tests/clojure/data/json_test.clj new file mode 100644 index 00000000..9e905279 --- /dev/null +++ b/test-resources/lib_tests/clojure/data/json_test.clj @@ -0,0 +1,425 @@ +(ns clojure.data.json-test + (:require [clojure.data.json :as json] + [clojure.test :refer :all] + [clojure.string :as str])) + +(deftest read-from-pushback-reader + (let [s (java.io.PushbackReader. (java.io.StringReader. "42"))] + (is (= 42 (json/read s))))) + +(deftest read-from-reader + (let [s (java.io.StringReader. "42")] + (is (= 42 (json/read s))))) + +(deftest read-numbers + (is (= 42 (json/read-str "42"))) + (is (= -3 (json/read-str "-3"))) + (is (= 3.14159 (json/read-str "3.14159"))) + (is (= 6.022e23 (json/read-str "6.022e23")))) + +(deftest read-bigint + (is (= 123456789012345678901234567890N + (json/read-str "123456789012345678901234567890")))) + +(deftest write-bigint + (is (= "123456789012345678901234567890" + (json/write-str 123456789012345678901234567890N)))) + +(deftest read-bigdec + (is (= 3.14159M (json/read-str "3.14159" :bigdec true)))) + +(deftest write-bigdec + (is (= "3.14159" (json/write-str 3.14159M)))) + +(deftest read-null + (is (= nil (json/read-str "null")))) + +(deftest read-strings + (is (= "Hello, World!" (json/read-str "\"Hello, World!\"")))) + +(deftest escaped-slashes-in-strings + (is (= "/foo/bar" (json/read-str "\"\\/foo\\/bar\"")))) + +(deftest unicode-escapes + (is (= " \u0beb " (json/read-str "\" \\u0bEb \"")))) + +(deftest unicode-outside-bmp + (is (= "\"smiling face: \uD83D\uDE03\"" + (json/write-str "smiling face: \uD83D\uDE03" :escape-unicode false))) + (is (= "\"smiling face: \\ud83d\\ude03\"" + (json/write-str "smiling face: \uD83D\uDE03" :escape-unicode true)))) + +(deftest escaped-whitespace + (is (= "foo\nbar" (json/read-str "\"foo\\nbar\""))) + (is (= "foo\rbar" (json/read-str "\"foo\\rbar\""))) + (is (= "foo\tbar" (json/read-str "\"foo\\tbar\"")))) + +(deftest read-booleans + (is (= true (json/read-str "true"))) + (is (= false (json/read-str "false")))) + +(deftest ignore-whitespace + (is (= nil (json/read-str "\r\n null")))) + +(deftest read-arrays + (is (= (vec (range 35)) + (json/read-str "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34]"))) + (is (= ["Ole" "Lena"] (json/read-str "[\"Ole\", \r\n \"Lena\"]")))) + +(deftest read-objects + (is (= {:k1 1, :k2 2, :k3 3, :k4 4, :k5 5, :k6 6, :k7 7, :k8 8 + :k9 9, :k10 10, :k11 11, :k12 12, :k13 13, :k14 14, :k15 15, :k16 16} + (json/read-str "{\"k1\": 1, \"k2\": 2, \"k3\": 3, \"k4\": 4, + \"k5\": 5, \"k6\": 6, \"k7\": 7, \"k8\": 8, + \"k9\": 9, \"k10\": 10, \"k11\": 11, \"k12\": 12, + \"k13\": 13, \"k14\": 14, \"k15\": 15, \"k16\": 16}" + :key-fn keyword)))) + +(deftest read-nested-structures + (is (= {:a [1 2 {:b [3 "four"]} 5.5]} + (json/read-str "{\"a\":[1,2,{\"b\":[3,\"four\"]},5.5]}" + :key-fn keyword)))) + +(deftest read-nested-structures-stream + (is (= {:a [1 2 {:b [3 "four"]} 5.5]} + (json/read (java.io.StringReader. "{\"a\":[1,2,{\"b\":[3,\"four\"]},5.5]}") + :key-fn keyword)))) + +(deftest reads-long-string-correctly + (let [long-string (str/join "" (take 100 (cycle "abcde")))] + (is (= long-string (json/read-str (str "\"" long-string "\"")))))) + +(deftest disallows-non-string-keys + (is (thrown? Exception (json/read-str "{26:\"z\"")))) + +(deftest disallows-barewords + (is (thrown? Exception (json/read-str " foo ")))) + +(deftest disallows-unclosed-arrays + (is (thrown? Exception (json/read-str "[1, 2, ")))) + +(deftest disallows-unclosed-objects + (is (thrown? Exception (json/read-str "{\"a\":1, ")))) + +(deftest disallows-empty-entry-in-object + (is (thrown? Exception (json/read-str "{\"a\":1,}"))) + (is (thrown? Exception (json/read-str "{\"a\":1, }"))) + (is (thrown? Exception (json/read-str "{\"a\":1,,,,}"))) + (is (thrown? Exception (json/read-str "{\"a\":1,,\"b\":2}")))) + +(deftest get-string-keys + (is (= {"a" [1 2 {"b" [3 "four"]} 5.5]} + (json/read-str "{\"a\":[1,2,{\"b\":[3,\"four\"]},5.5]}")))) + +(deftest keywordize-keys + (is (= {:a [1 2 {:b [3 "four"]} 5.5]} + (json/read-str "{\"a\":[1,2,{\"b\":[3,\"four\"]},5.5]}" + :key-fn keyword)))) + +(deftest convert-values + (is (= {:number 42 :date (java.sql.Date. 55 6 12)} + (json/read-str "{\"number\": 42, \"date\": \"1955-07-12\"}" + :key-fn keyword + :value-fn (fn [k v] + (if (= :date k) + (java.sql.Date/valueOf v) + v)))))) + +(deftest omit-values + (is (= {:number 42} + (json/read-str "{\"number\": 42, \"date\": \"1955-07-12\"}" + :key-fn keyword + :value-fn (fn thisfn [k v] + (if (= :date k) + thisfn + v))))) + (is (= "{\"c\":1,\"e\":2}" + (json/write-str (sorted-map :a nil, :b nil, :c 1, :d nil, :e 2, :f nil) + :value-fn (fn remove-nils [k v] + (if (nil? v) + remove-nils + v)))))) + +(declare pass1-string) + +(deftest pass1-test + (let [input (json/read-str pass1-string)] + (is (= "JSON Test Pattern pass1" (first input))) + (is (= "array with 1 element" (get-in input [1 "object with 1 member" 0]))) + (is (= 1234567890 (get-in input [8 "integer"]))) + (is (= "rosebud" (last input))))) + +; from http://www.json.org/JSON_checker/test/pass1.json +(def pass1-string + "[ + \"JSON Test Pattern pass1\", + {\"object with 1 member\":[\"array with 1 element\"]}, + {}, + [], + -42, + true, + false, + null, + { + \"integer\": 1234567890, + \"real\": -9876.543210, + \"e\": 0.123456789e-12, + \"E\": 1.234567890E+34, + \"\": 23456789012E66, + \"zero\": 0, + \"one\": 1, + \"space\": \" \", + \"quote\": \"\\\"\", + \"backslash\": \"\\\\\", + \"controls\": \"\\b\\f\\n\\r\\t\", + \"slash\": \"/ & \\/\", + \"alpha\": \"abcdefghijklmnopqrstuvwyz\", + \"ALPHA\": \"ABCDEFGHIJKLMNOPQRSTUVWYZ\", + \"digit\": \"0123456789\", + \"0123456789\": \"digit\", + \"special\": \"`1~!@#$%^&*()_+-={':[,]}|;.?\", + \"hex\": \"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\", + \"true\": true, + \"false\": false, + \"null\": null, + \"array\":[ ], + \"object\":{ }, + \"address\": \"50 St. James Street\", + \"url\": \"http://www.JSON.org/\", + \"comment\": \"// /* */\": \" \", + \" s p a c e d \" :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],\"compact\":[1,2,3,4,5,6,7], + \"jsontext\": \"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\", + \"quotes\": \"" \\u0022 %22 0x22 034 "\", + \"\\/\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?\" +: \"A key can be any string\" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,\"rosebud\"]") + + +(deftest print-json-strings + (is (= "\"Hello, World!\"" (json/write-str "Hello, World!"))) + (is (= "\"\\\"Embedded\\\" Quotes\"" (json/write-str "\"Embedded\" Quotes")))) + +(deftest print-unicode + (is (= "\"\\u1234\\u4567\"" (json/write-str "\u1234\u4567")))) + +(deftest print-nonescaped-unicode + (is (= "\"\\u0000\\t\\u001f \"" (json/write-str "\u0000\u0009\u001f\u0020" :escape-unicode true))) + (is (= "\"\\u0000\\t\\u001f \"" (json/write-str "\u0000\u0009\u001f\u0020" :escape-unicode false))) + (is (= "\"\u1234\u4567\"" (json/write-str "\u1234\u4567" :escape-unicode false)))) + +(deftest escape-special-separators + (is (= "\"\\u2028\\u2029\"" (json/write-str "\u2028\u2029" :escape-unicode false))) + (is (= "\"\u2028\u2029\"" (json/write-str "\u2028\u2029" :escape-js-separators false)))) + +(deftest print-json-null + (is (= "null" (json/write-str nil)))) + +(deftest print-ratios-as-doubles + (is (= "0.75" (json/write-str 3/4)))) + +(deftest print-bigints + (is (= "12345678901234567890" (json/write-str 12345678901234567890)))) + +(deftest print-uuids + (let [uid (java.util.UUID/randomUUID) + roundtripped (java.util.UUID/fromString (json/read-str (json/write-str uid)))] + (is (= uid roundtripped)))) + +#_(def ^java.text.SimpleDateFormat date-format + (doto (java.text.SimpleDateFormat. "dd-MM-yyyy hh:mm:ss") + (.setTimeZone (java.util.TimeZone/getDefault)))) + +#_(deftest print-util-date + (let [date (.parse date-format "24-03-2006 15:49:00") + epoch-millis (.getTime date)] + (is (= epoch-millis (-> date + json/write-str + json/read-str + java.time.Instant/parse + .toEpochMilli))))) + +#_(deftest print-sql-date + (let [date (.parse date-format "24-03-2006 15:49:00") + sql-date (java.sql.Date. (.getTime date)) + epoch-millis-start-of-day (.getTime (.getTime (doto (java.util.Calendar/getInstance) + (.setTime date) + (.set java.util.Calendar/HOUR_OF_DAY 0) + (.set java.util.Calendar/MINUTE 0) + (.set java.util.Calendar/SECOND 0) + (.set java.util.Calendar/MILLISECOND 0))))] + (is (= epoch-millis-start-of-day (-> sql-date + json/write-str + json/read-str + java.time.Instant/parse + .toEpochMilli))))) + +(deftest print-time + (let [time (java.time.Instant/parse "2006-03-24T15:49:00.000Z")] + (is (= time (java.time.Instant/parse (json/read-str (json/write-str time))))))) + + +#_(deftest print-time-supports-format + (let [formatter (.withZone java.time.format.DateTimeFormatter/ISO_ZONED_DATE_TIME + (java.time.ZoneId/systemDefault)) + date (.parse date-format "24-03-2006 15:49:00") + time (.toInstant (.atZone (java.time.LocalDateTime/parse + "2006-03-24T15:49:00.000Z" + formatter) + (java.time.ZoneId/systemDefault)))] + (is (= time (->> (json/write-str date :date-formatter formatter) + json/read-str + (.parse formatter) + (java.time.Instant/from)))))) + +(deftest error-on-NaN + (is (thrown? Exception (json/write-str Float/NaN))) + (is (thrown? Exception (json/write-str Double/NaN)))) + +(deftest error-on-infinity + (is (thrown? Exception (json/write-str Float/POSITIVE_INFINITY))) + (is (thrown? Exception (json/write-str Float/NEGATIVE_INFINITY))) + (is (thrown? Exception (json/write-str Double/POSITIVE_INFINITY))) + (is (thrown? Exception (json/write-str Double/NEGATIVE_INFINITY)))) + +(defn- double-value [_ v] + (if (and (instance? Double v) + (or (.isNaN ^Double v) + (.isInfinite ^Double v))) + (str v) + v)) + +(deftest special-handler-for-double-NaN + (is (= "{\"double\":\"NaN\"}" + (json/write-str {:double Double/NaN} + :value-fn double-value)))) + +(deftest special-handler-for-double-infinity + (is (= "{\"double\":\"Infinity\"}" + (json/write-str {:double Double/POSITIVE_INFINITY} + :value-fn double-value))) + (is (= "{\"double\":\"-Infinity\"}" + (json/write-str {:double Double/NEGATIVE_INFINITY} + :value-fn double-value)))) + +(deftest print-json-arrays + (is (= "[1,2,3]" (json/write-str [1 2 3]))) + (is (= "[1,2,3]" (json/write-str (list 1 2 3)))) + (is (= "[1,2,3]" (json/write-str (sorted-set 1 2 3)))) + (is (= "[1,2,3]" (json/write-str (seq [1 2 3]))))) + +(deftest print-java-arrays + (is (= "[1,2,3]" (json/write-str (into-array [1 2 3]))))) + +(deftest print-empty-arrays + (is (= "[]" (json/write-str []))) + (is (= "[]" (json/write-str (list)))) + (is (= "[]" (json/write-str #{})))) + +(deftest print-json-objects + (is (= "{\"a\":1,\"b\":2}" (json/write-str (sorted-map :a 1 :b 2))))) + +(deftest object-keys-must-be-strings + (is (= "{\"1\":1,\"2\":2}" (json/write-str (sorted-map 1 1 2 2))))) + +(deftest print-empty-objects + (is (= "{}" (json/write-str {})))) + +(deftest accept-sequence-of-nils + (is (= "[null,null,null]" (json/write-str [nil nil nil])))) + +(deftest error-on-nil-keys + (is (thrown? Exception (json/write-str {nil 1})))) + +(deftest characters-in-symbols-are-escaped + (is (= "\"foo\\u1b1b\"" (json/write-str (symbol "foo\u1b1b"))))) + +(deftest default-throws-on-eof + (is (thrown? java.io.EOFException (json/read-str "")))) + +(deftest throws-eof-in-unterminated-array + (is (thrown? java.io.EOFException + (json/read-str "[1, ")))) + +(deftest throws-eof-in-unterminated-string + (is (thrown? java.io.EOFException + (json/read-str "\"missing end quote")))) + +(deftest throws-eof-in-escaped-chars + (is (thrown? java.io.EOFException + (json/read-str "\"\\")))) + +(deftest accept-eof + (is (= ::eof (json/read-str "" :eof-error? false :eof-value ::eof)))) + +(deftest characters-in-map-keys-are-escaped + (is (= "{\"\\\"\":42}" (json/write-str {"\"" 42})))) + +;;; Indent + +(deftest print-json-arrays-indent + (is (= "[\n 1,\n 2,\n 3\n]" (json/write-str [1 2 3] :indent true))) + (is (= "[\n 1,\n 2,\n 3\n]" (json/write-str (list 1 2 3) :indent true))) + (is (= "[\n 1,\n 2,\n 3\n]" (json/write-str (sorted-set 1 2 3) :indent true))) + (is (= "[\n 1,\n 2,\n 3\n]" (json/write-str (seq [1 2 3]) :indent true)))) + +(deftest print-java-arrays-indent + (is (= "[\n 1,\n 2,\n 3\n]" (json/write-str (into-array [1 2 3]) :indent true)))) + +(deftest print-empty-arrays-indent + (is (= "[]" (json/write-str [] :indent true))) + (is (= "[]" (json/write-str (list) :indent true))) + (is (= "[]" (json/write-str #{} :indent true)))) + +(deftest print-json-objects-indent + (is (= "{\n \"a\": 1,\n \"b\": 2\n}" (json/write-str (sorted-map :a 1 :b 2) :indent true)))) + +(deftest print-empty-objects-indent + (is (= "{}" (json/write-str {} :indent true)))) + +(deftest print-json-nested-indent + (is (= +"{ + \"a\": { + \"b\": [ + 1, + 2 + ], + \"c\": [], + \"d\": {} + } +}" (json/write-str {:a (sorted-map :b [1 2] :c [] :d {})} :indent true)))) + + +;;; Pretty-printer + +(deftest pretty-printing + (let [x (json/read-str pass1-string)] + (is (= x (json/read-str (with-out-str (json/pprint x))))))) + +(deftest pretty-print-nonescaped-unicode + (is (= (str "\"\u1234\u4567\"" (System/lineSeparator)) + (with-out-str + (json/pprint "\u1234\u4567" :escape-unicode false))))) + +(defn benchmark [] + (dotimes [_ 8] + (time + (dotimes [_ 1000] + (assert (= (json/read-str pass1-string) + (json/read-str (json/write-str (json/read-str pass1-string))))))))) diff --git a/test-resources/lib_tests/clojure/data/json_test_suite_test.clj b/test-resources/lib_tests/clojure/data/json_test_suite_test.clj new file mode 100644 index 00000000..c0b714c3 --- /dev/null +++ b/test-resources/lib_tests/clojure/data/json_test_suite_test.clj @@ -0,0 +1,393 @@ +(ns clojure.data.json-test-suite-test + (:require [clojure.data.json :as json] + [clojure.test :refer :all] + [clojure.string :as str])) + +(deftest i-number-double-huge-neg-exp-test + (is (= [0.0] (json/read-str "[123.456e-789]")))) + +(deftest i-number-huge-exp-test + (is (= [##Inf] + (json/read-str "[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]")))) + +(deftest i-number-neg-int-huge-exp-test + (is (= [##-Inf] (json/read-str "[-1e+9999]")))) + +(deftest i-number-pos-double-huge-exp-test + (is (= [##Inf] (json/read-str "[1.5e+9999]")))) + +(deftest i-number-real-neg-overflow-test + (is (= [##-Inf] (json/read-str "[-123123e100000]")))) + +(deftest i-number-real-pos-overflow-test + (is (= [##Inf] (json/read-str "[123123e100000]")))) + +(deftest i-number-real-underflow-test + (is (= [0.0] (json/read-str "[123e-10000000]")))) + +(deftest i-number-too-big-neg-int-test + (is (= [-123123123123123123123123123123N] + (json/read-str "[-123123123123123123123123123123]")))) + +(deftest i-number-too-big-pos-int-test + (is (= [100000000000000000000N] (json/read-str "[100000000000000000000]")))) + +(deftest i-number-very-big-negative-int-test + (is (= [-237462374673276894279832749832423479823246327846N] + (json/read-str "[-237462374673276894279832749832423479823246327846]")))) + +(deftest n-array-1-true-without-comma-test + (is (thrown? Exception (json/read-str "[1 true]")))) + +(deftest n-array-colon-instead-of-comma-test + (is (thrown? Exception (json/read-str "[\"\": 1]")))) + +(deftest n-array-comma-and-number-test + (is (thrown? Exception (json/read-str "[,1]")))) + +(deftest n-array-double-comma-test + (is (thrown? Exception (json/read-str "[1,,2]")))) + +(deftest n-array-double-extra-comma-test + (is (thrown? Exception (json/read-str "[\"x\",,]")))) + +(deftest n-array-extra-comma-test + (is (thrown? Exception (json/read-str "[\"\",]")))) + +(deftest n-array-incomplete-invalid-value-test + (is (thrown? Exception (json/read-str "[x")))) + +(deftest n-array-incomplete-test + (is (thrown? Exception (json/read-str "[\"x\"")))) + +(deftest n-array-inner-array-no-comma-test + (is (thrown? Exception (json/read-str "[3[4]]")))) + +(deftest n-array-items-separated-by-semicolon-test + (is (thrown? Exception (json/read-str "[1:2]")))) + +(deftest n-array-just-comma-test + (is (thrown? Exception (json/read-str "[,]")))) + +(deftest n-array-just-minus-test + (is (thrown? Exception (json/read-str "[-]")))) + +(deftest n-array-missing-value-test + (is (thrown? Exception (json/read-str "[ , \"\"]")))) + +(deftest n-array-newlines-unclosed-test + (is (thrown? Exception (json/read-str "[\"a\",\n4\n,1,")))) + +(deftest n-array-number-and-comma-test + (is (thrown? Exception (json/read-str "[1,]")))) + +(deftest n-array-number-and-several-commas-test + (is (thrown? Exception (json/read-str "[1,,]")))) + +(deftest n-array-spaces-vertical-tab-formfeed-test + (is (thrown? Exception (json/read-str "[\" a\"\\f]")))) + +(deftest n-array-star-inside-test + (is (thrown? Exception (json/read-str "[*]")))) + +(deftest n-array-unclosed-test + (is (thrown? Exception (json/read-str "[\"\"")))) + +(deftest n-array-unclosed-trailing-comma-test + (is (thrown? Exception (json/read-str "[1,")))) + +(deftest n-array-unclosed-with-new-lines-test + (is (thrown? Exception (json/read-str "[1,\n1\n,1")))) + +(deftest n-array-unclosed-with-object-inside-test + (is (thrown? Exception (json/read-str "[{}")))) + +(deftest n-number-++-test + (is (thrown? Exception (json/read-str "[++1234]")))) + +(deftest n-number-+1-test + (is (thrown? Exception (json/read-str "[+1]")))) + +(deftest n-number-+Inf-test + (is (thrown? Exception (json/read-str "[+Inf]")))) + +(deftest n-number--01-test + (is (thrown? Exception (json/read-str "[-01]")))) + +(deftest n-number--1.0.-test + (is (thrown? Exception (json/read-str "[-1.0.]")))) + +(deftest n-number--2.-test + (is (thrown? Exception (json/read-str "[-2.]")))) + +(deftest n-number--NaN-test + (is (thrown? Exception (json/read-str "[-NaN]")))) + +(deftest n-number-.-1-test + (is (thrown? Exception (json/read-str "[.-1]")))) + +(deftest n-number-.2e-3-test + (is (thrown? Exception (json/read-str "[.2e-3]")))) + +(deftest n-number-0-capital-E+-test + (is (thrown? Exception (json/read-str "[0E+]")))) + +(deftest n-number-0-capital-E-test + (is (thrown? Exception (json/read-str "[0E]")))) + +(deftest n-number-0.1.2-test + (is (thrown? Exception (json/read-str "[0.1.2]")))) + +(deftest n-number-0.3e+-test + (is (thrown? Exception (json/read-str "[0.3e+]")))) + +(deftest n-number-0.3e-test + (is (thrown? Exception (json/read-str "[0.3e]")))) + +(deftest n-number-0.e1-test + (is (thrown? Exception (json/read-str "[0.e1]")))) + +(deftest n-number-0e+-test + (is (thrown? Exception (json/read-str "[0e+]")))) + +(deftest n-number-0e-test + (is (thrown? Exception (json/read-str "[0e]")))) + +(deftest n-number-1-000-test + (is (thrown? Exception (json/read-str "[1 000.0]")))) + +(deftest n-number-1.0e+-test + (is (thrown? Exception (json/read-str "[1.0e+]")))) + +(deftest n-number-1.0e--test + (is (thrown? Exception (json/read-str "[1.0e-]")))) + +(deftest n-number-1.0e-test + (is (thrown? Exception (json/read-str "[1.0e]")))) + +(deftest n-number-1eE2-test + (is (thrown? Exception (json/read-str "[1eE2]")))) + +(deftest n-number-2.e+3-test + (is (thrown? Exception (json/read-str "[2.e+3]")))) + +(deftest n-number-2.e-3-test + (is (thrown? Exception (json/read-str "[2.e-3]")))) + +(deftest n-number-2.e3-test + (is (thrown? Exception (json/read-str "[2.e3]")))) + +(deftest n-number-9.e+-test + (is (thrown? Exception (json/read-str "[9.e+]")))) + +(deftest n-number-Inf-test + (is (thrown? Exception (json/read-str "[Inf]")))) + +(deftest n-number-NaN-test + (is (thrown? Exception (json/read-str "[NaN]")))) + +(deftest n-number-expression-test + (is (thrown? Exception (json/read-str "[1+2]")))) + +(deftest n-number-hex-1-digit-test + (is (thrown? Exception (json/read-str "[0x1]")))) + +(deftest n-number-hex-2-digits-test + (is (thrown? Exception (json/read-str "[0x42]")))) + +(deftest n-number-infinity-test + (is (thrown? Exception (json/read-str "[Infinity]")))) + +(deftest n-number-invalid+--test + (is (thrown? Exception (json/read-str "[0e+-1]")))) + +(deftest n-number-invalid-negative-real-test + (is (thrown? Exception (json/read-str "[-123.123foo]")))) + +(deftest n-number-minus-infinity-test + (is (thrown? Exception (json/read-str "[-Infinity]")))) + +(deftest n-number-minus-sign-with-trailing-garbage-test + (is (thrown? Exception (json/read-str "[-foo]")))) + +(deftest n-number-minus-space-1-test + (is (thrown? Exception (json/read-str "[- 1]")))) + +(deftest n-number-neg-int-starting-with-zero-test + (is (thrown? Exception (json/read-str "[-012]")))) + +(deftest n-number-neg-real-without-int-part-test + (is (thrown? Exception (json/read-str "[-.123]")))) + +(deftest n-number-neg-with-garbage-at-end-test + (is (thrown? Exception (json/read-str "[-1x]")))) + +(deftest n-number-real-garbage-after-e-test + (is (thrown? Exception (json/read-str "[1ea]")))) + +(deftest n-number-real-without-fractional-part-test + (is (thrown? Exception (json/read-str "[1.]")))) + +(deftest n-number-starting-with-dot-test + (is (thrown? Exception (json/read-str "[.123]")))) + +(deftest n-number-with-alpha-char-test + (is (thrown? Exception (json/read-str "[1.8011670033376514H-308]")))) + +(deftest n-number-with-alpha-test + (is (thrown? Exception (json/read-str "[1.2a-3]")))) + +(deftest n-number-with-leading-zero-test + (is (thrown? Exception (json/read-str "[012]")))) + +(deftest n-object-non-string-key-but-huge-number-instead-test + (is (thrown? Exception (json/read-str "{9999E9999:1}")))) + +(deftest n-structure-array-with-unclosed-string-test + (is (thrown? Exception (json/read-str "[\"asd]")))) + +(deftest n-structure-end-array-test + (is (thrown? Exception (json/read-str "]")))) + +(deftest n-structure-number-with-trailing-garbage-test + (is (thrown? Exception (json/read-str "2@")))) + +(deftest n-structure-open-array-apostrophe-test + (is (thrown? Exception (json/read-str "['")))) + +(deftest n-structure-open-array-comma-test + (is (thrown? Exception (json/read-str "[,")))) + +(deftest n-structure-open-array-open-object-test + (is (thrown? Exception (json/read-str "[{")))) + +(deftest n-structure-open-array-open-string-test + (is (thrown? Exception (json/read-str "[\"a")))) + +(deftest n-structure-open-array-string-test + (is (thrown? Exception (json/read-str "[\"a\"")))) + +(deftest n-structure-open-object-close-array-test + (is (thrown? Exception (json/read-str "{]")))) + +(deftest n-structure-open-object-open-array-test + (is (thrown? Exception (json/read-str "{[")))) + +(deftest n-structure-unclosed-array-partial-null-test + (is (thrown? Exception (json/read-str "[ false, nul")))) + +(deftest n-structure-unclosed-array-test + (is (thrown? Exception (json/read-str "[1")))) + +(deftest n-structure-unclosed-array-unfinished-false-test + (is (thrown? Exception (json/read-str "[ true, fals")))) + +(deftest n-structure-unclosed-array-unfinished-true-test + (is (thrown? Exception (json/read-str "[ false, tru")))) + +(deftest y-array-arraysWithSpaces-test + (is (= [[]] (json/read-str "[[] ]")))) + +(deftest y-array-empty-string-test + (is (= [""] (json/read-str "[\"\"]")))) + +(deftest y-array-empty-test + (is (= [] (json/read-str "[]")))) + +(deftest y-array-ending-with-newline-test + (is (= ["a"] (json/read-str "[\"a\"]")))) + +(deftest y-array-false-test + (is (= [false] (json/read-str "[false]")))) + +(deftest y-array-heterogeneous-test + (is (= [nil 1 "1" {}] (json/read-str "[null, 1, \"1\", {}]")))) + +(deftest y-array-null-test + (is (= [nil] (json/read-str "[null]")))) + +(deftest y-array-with-1-and-newline-test + (is (= [1] (json/read-str "[1\n]")))) + +(deftest y-array-with-leading-space-test + (is (= [1] (json/read-str " [1]")))) + +(deftest y-array-with-several-null-test + (is (= [1 nil nil nil 2] (json/read-str "[1,null,null,null,2]")))) + +(deftest y-array-with-trailing-space-test + (is (= [2] (json/read-str "[2] ")))) + +(deftest y-number-0e+1-test + (is (= [0.0] (json/read-str "[0e+1]")))) + +(deftest y-number-0e1-test + (is (= [0.0] (json/read-str "[0e1]")))) + +(deftest y-number-after-space-test + (is (= [4] (json/read-str "[ 4]")))) + +(deftest y-number-double-close-to-zero-test + (is (= [-1.0E-78] + (json/read-str "[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]")))) + +(deftest y-number-int-with-exp-test + (is (= [200.0] (json/read-str "[20e1]")))) + +(deftest y-number-minus-zero-test + (is (= [0] (json/read-str "[-0]")))) + +(deftest y-number-negative-int-test + (is (= [-123] (json/read-str "[-123]")))) + +(deftest y-number-negative-one-test + (is (= [-1] (json/read-str "[-1]")))) + +(deftest y-number-negative-zero-test + (is (= [0] (json/read-str "[-0]")))) + +(deftest y-number-real-capital-e-neg-exp-test + (is (= [0.01] (json/read-str "[1E-2]")))) + +(deftest y-number-real-capital-e-pos-exp-test + (is (= [100.0] (json/read-str "[1E+2]")))) + +(deftest y-number-real-capital-e-test + (is (= [1.0E22] (json/read-str "[1E22]")))) + +(deftest y-number-real-exponent-test + (is (= [1.23E47] (json/read-str "[123e45]")))) + +(deftest y-number-real-fraction-exponent-test + (is (= [1.23456E80] (json/read-str "[123.456e78]")))) + +(deftest y-number-real-neg-exp-test + (is (= [0.01] (json/read-str "[1e-2]")))) + +(deftest y-number-real-pos-exponent-test + (is (= [100.0] (json/read-str "[1e+2]")))) + +(deftest y-number-simple-int-test + (is (= [123] (json/read-str "[123]")))) + +(deftest y-number-simple-real-test + (is (= [123.456789] (json/read-str "[123.456789]")))) + +(deftest y-number-test + (is (= [1.23E67] (json/read-str "[123e65]")))) + +(deftest y-object-extreme-numbers-test + (is (= {"min" -1.0E28, "max" 1.0E28} + (json/read-str "{\"min\": -1.0e+28, \"max\": 1.0e+28}")))) + +(deftest y-string-in-array-test + (is (= ["asd"] (json/read-str "[\"asd\"]")))) + +(deftest y-string-in-array-with-leading-space-test + (is (= ["asd"] (json/read-str "[ \"asd\"]")))) + +(deftest y-structure-true-in-array-test + (is (= [true] (json/read-str "[true]")))) + +(deftest y-structure-whitespace-array-test + (is (= [] (json/read-str " [] ")))) diff --git a/test-resources/lib_tests/crispin/core_test.clj b/test-resources/lib_tests/crispin/core_test.clj new file mode 100644 index 00000000..fbb6e7f6 --- /dev/null +++ b/test-resources/lib_tests/crispin/core_test.clj @@ -0,0 +1,22 @@ +(ns crispin.core-test + (:require [clojure.test :refer [deftest is testing]] + [crispin.core :as cfg])) + +(deftest crispin.core-test + (testing "config from multiple sources" + (do + (cfg/load-custom-cfg! "test-resources/lib_tests/crispin" "crispin-test-custom-cfg.edn") + (System/setProperty "crispintest.value" "yes") + (System/setProperty "crispin" "test-resources/lib_tests/crispin/crispin-test-cfg.edn") + (let [c (cfg/cfg)] + ; something from the environment + (is (not-empty (cfg/sget c :path))) + ; things from the resource named by the :crispin property + (is (= "pina colada" (cfg/sget-in c [:likes 0]))) + (is (= 3.14 (cfg/nget-in c [:crispintest :pi]))) + ; something from system properties + (is (true? (cfg/bget-in c [:crispintest :value]))) + ; something from load-custom-cfg! file + (is (= :bar (:foo c)))) + (System/clearProperty "crispintest.value") + (System/clearProperty "crispin")))) diff --git a/test-resources/lib_tests/crispin/crispin-test-cfg.edn b/test-resources/lib_tests/crispin/crispin-test-cfg.edn new file mode 100644 index 00000000..303c2ac9 --- /dev/null +++ b/test-resources/lib_tests/crispin/crispin-test-cfg.edn @@ -0,0 +1,2 @@ +{:crispintest {:pi 3.14} + :likes ["pina colada" "getting caught in the rain"]} diff --git a/test-resources/lib_tests/crispin/crispin-test-custom-cfg.edn b/test-resources/lib_tests/crispin/crispin-test-custom-cfg.edn new file mode 100644 index 00000000..cf1c0f08 --- /dev/null +++ b/test-resources/lib_tests/crispin/crispin-test-custom-cfg.edn @@ -0,0 +1 @@ +{:foo :bar} diff --git a/test-resources/lib_tests/hasch/test.cljc b/test-resources/lib_tests/hasch/test.cljc new file mode 100644 index 00000000..48c3ba63 --- /dev/null +++ b/test-resources/lib_tests/hasch/test.cljc @@ -0,0 +1,147 @@ +(ns hasch.test + (:require [hasch.core :refer [edn-hash uuid squuid b64-hash]] + [hasch.benc :refer [xor-hashes]] + [hasch.md5 :as md5] + [hasch.hex :as hex] + [hasch.platform :refer [uuid5 sha512-message-digest hash->str #?(:cljs utf8)]] + [incognito.base :as ic] + #?(:clj [clojure.test :as t :refer (is deftest run-tests testing)] + :cljs [cljs.test :as t :refer-macros (is deftest run-tests testing)]))) + +#?(:cljs (def byte-array into-array)) + +(defrecord Bar [name]) + +(deftest hash-test + (testing "Basic hash coercions of EDN primitives." + (is (= (edn-hash nil) + '(184 36 77 2 137 129 214 147 175 123 69 106 248 239 164 202 214 61 40 46 25 255 20 148 44 36 110 80 217 53 29 34 112 74 128 42 113 195 88 11 99 112 222 76 235 41 60 50 74 132 35 52 37 87 212 229 195 132 56 240 227 105 16 238))) + + (is (= (edn-hash true) + '(221 223 252 44 103 48 51 199 71 184 156 187 201 140 35 99 235 153 185 70 157 229 122 4 111 90 12 150 43 67 185 166 210 79 54 62 117 173 76 252 187 67 163 85 202 124 63 252 109 44 47 70 74 129 52 241 35 15 116 253 241 141 50 131))) + + (is (= (edn-hash false) + '(54 0 110 63 158 137 176 89 220 235 107 213 84 159 27 25 148 206 193 96 192 73 41 255 220 181 215 106 208 220 173 69 213 190 181 70 141 193 1 225 188 142 127 176 102 61 13 54 151 161 195 158 152 190 212 168 91 43 153 108 122 123 90 32))) + + (is (= (edn-hash \f) + '(211 133 203 224 194 174 136 44 216 77 98 85 54 188 116 101 139 174 40 108 48 180 235 231 214 189 34 246 32 30 56 45 179 218 36 206 61 191 79 160 212 162 212 226 235 17 27 228 218 74 17 229 9 147 187 232 35 244 179 233 66 165 152 253))) + + (is (= (edn-hash \ä) + '(51 232 113 238 243 104 216 10 143 88 143 111 122 220 35 138 251 22 8 130 238 73 253 62 143 207 208 45 116 21 120 18 253 34 160 30 144 46 182 7 160 254 197 120 199 220 140 209 3 66 25 214 131 145 17 222 28 157 22 103 226 254 178 186))) + + (is (= (edn-hash "hello") + '(178 114 9 243 3 150 0 132 236 216 60 87 108 34 2 35 85 37 203 202 97 176 9 55 25 191 143 251 251 47 49 139 99 191 77 63 167 158 61 183 233 59 43 57 16 252 121 198 65 201 112 167 96 61 134 122 177 149 45 87 233 23 173 192))) + + (is (= (edn-hash "小鳩ちゃんかわいいなぁ") + '(2 191 84 39 34 44 227 102 135 109 17 136 159 80 253 7 40 0 170 134 198 204 137 10 194 21 113 203 2 87 125 80 172 165 111 110 222 7 123 138 148 124 207 180 240 207 91 6 248 28 53 168 143 30 106 103 101 82 133 215 69 35 93 47))) + + (is (= (edn-hash "😡😡😡") + '(18 17 129 25 13 183 170 164 178 18 97 0 123 151 164 145 95 197 214 178 107 96 255 105 255 104 69 21 205 160 13 222 9 55 63 37 174 33 35 86 204 73 17 110 82 107 151 64 63 79 191 246 177 76 71 24 107 44 43 156 178 169 195 214))) + + (is (= (edn-hash (int 1234567890)) + (edn-hash (long 1234567890)) + #?(:clj (edn-hash (biginteger "1234567890"))) + #?(:clj (edn-hash (bigint "1234567890"))) + '(65 199 158 164 193 95 213 144 233 29 41 86 123 106 110 215 117 225 149 249 204 124 220 217 226 120 131 178 61 133 39 228 182 233 235 249 10 249 141 122 101 25 46 134 18 222 175 224 134 61 167 114 15 109 2 146 38 65 1 55 128 137 144 55))) + + (is (= (edn-hash (double 123.1)) + (edn-hash (float 123.1)) + '(155 181 33 252 126 113 188 20 210 155 50 24 125 212 205 160 135 108 90 43 154 65 61 229 226 83 11 110 64 61 124 45 43 186 152 127 64 171 171 154 28 149 180 136 229 69 195 145 126 99 56 14 48 194 180 126 212 83 123 206 36 189 189 167) + #?(:clj (edn-hash (BigDecimal. "123.1"))))) + + (is (= (edn-hash :core/test) + '(62 51 214 78 41 84 37 205 69 197 105 26 235 55 30 87 46 117 187 194 101 184 139 244 111 232 98 175 16 174 182 211 11 171 154 64 90 18 229 93 188 246 33 234 102 145 68 30 92 0 81 208 210 10 124 137 203 18 249 138 226 253 60 62))) + + (is (= (edn-hash #uuid "242525f1-8ed7-5979-9232-6992dd1e11e4") + '(42 243 183 237 233 94 246 1 110 56 231 49 64 217 181 17 108 11 120 199 223 53 149 47 49 8 109 94 127 93 250 51 167 211 25 31 3 171 149 67 23 245 38 248 40 31 199 211 162 242 120 99 187 6 29 237 53 174 22 192 27 159 227 164))) + + (is (= (edn-hash (#?(:clj java.util.Date. :cljs js/Date.) 1000000000000)) + '(177 226 212 235 221 67 176 34 184 69 101 45 117 193 95 187 54 50 210 149 10 193 10 67 220 174 25 99 176 115 250 216 29 49 148 167 52 86 203 90 30 170 62 149 115 102 109 120 128 62 2 213 188 41 203 91 202 106 142 100 119 160 26 3))) + + (is (= (edn-hash 'core/+) + '(164 63 64 77 190 144 72 80 34 36 254 237 101 99 57 114 54 44 195 22 255 11 242 114 99 87 99 135 103 73 164 183 20 192 184 54 183 244 192 151 88 96 55 204 73 156 73 92 154 8 248 205 119 157 34 112 202 51 52 169 162 61 91 235))) + + (is (= (edn-hash '(1 2 3)) + '(244 105 186 110 183 117 195 78 70 57 251 132 133 114 134 175 228 94 242 41 194 191 186 237 163 178 255 193 141 120 5 137 223 130 170 47 231 133 78 131 128 194 115 140 186 169 124 71 205 210 228 236 82 97 166 158 190 98 106 80 237 149 96 102))) + + (is (= (edn-hash [1 2 3 4]) + '(172 52 37 123 179 106 243 207 88 177 218 22 170 25 13 155 205 89 156 251 253 50 3 3 191 74 229 97 252 37 162 240 197 252 240 199 177 8 96 227 121 100 106 132 68 227 175 189 247 184 108 25 117 154 186 63 108 4 210 20 75 25 239 199))) + + (is (= (edn-hash {:a "hello" + :balloon "world"}) + '(135 204 255 206 109 55 248 198 218 226 173 91 27 244 68 34 108 207 62 12 114 49 69 90 22 44 155 178 212 188 139 50 217 200 63 207 14 112 179 94 202 96 196 139 202 154 214 211 182 97 31 139 49 153 203 233 240 223 154 161 78 131 159 102))) + + (is (= (edn-hash #{1 2 3 4}) + '(42 216 217 238 97 125 210 112 2 83 128 62 82 47 119 14 59 95 246 107 191 138 251 102 201 52 9 132 96 243 199 223 218 81 88 130 165 214 125 48 222 30 64 233 101 122 196 84 11 93 186 26 92 225 203 161 196 98 186 138 174 118 244 248))) + + (is (= #_(edn-hash (Bar. "hello")) + #_(edn-hash (ic/incognito-reader {'hasch.test.Bar map->Bar} + (ic/incognito-writer {} (Bar. "hello")))) + #_(edn-hash (ic/map->IncognitoTaggedLiteral (ic/incognito-writer {} (Bar. "hello")))) + (edn-hash (ic/map->IncognitoTaggedLiteral {:tag 'hasch.test.Bar + :value {:name "hello"}})) + '(194 16 151 144 95 224 245 28 219 137 32 192 218 166 162 177 32 154 132 5 111 169 220 211 204 164 67 231 51 96 248 217 77 78 28 136 150 212 202 152 45 167 120 241 14 152 250 246 187 113 212 216 204 46 163 107 91 24 91 0 72 38 4 31))) + + (is (= (edn-hash #?(:cljs (js/Uint8Array. #js [1 2 3 42 149]) + :clj (byte-array [1 2 3 42 149]))) + '(135 209 248 171 162 90 41 221 173 216 64 218 222 93 242 60 243 5 190 153 101 194 74 130 55 184 84 148 167 94 210 250 140 211 6 234 221 25 113 83 153 75 180 4 194 163 178 197 243 126 27 172 248 169 161 90 102 172 160 98 249 32 42 157))))) + +(deftest padded-coercion + (testing "Padded xor coercion for commutative collections." + (is (= (map byte + (xor-hashes (map byte-array + [[0xa0 0x01 0xf3] [0x0c 0xf0 0x5f] [0x0a 0x30 0x07]]))) + (map byte (xor-hashes (map byte-array + [[0x0a 0x30 0x07] [0x0c 0xf0 0x5f] [0xa0 0x01 0xf3]]))))))) + + +(deftest code-hashing + (testing "Code hashing." + (is (= (-> '(fn fib [n] + (if (or (= n 0) (= n 1)) 1 + (+ (fib (- n 1)) (fib (- n 2))))) + edn-hash + uuid5) + #uuid "386eabb0-8adc-52a2-a715-5a74c9197646")))) + +(deftest hash-stringification + (testing "Stringification." + (is (= (hash->str (range 256)) + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")))) + +(deftest squuid-test + (testing "Sequential UUID functionality." + (is (= (subs (str (squuid (uuid [1 2 3]))) 8) + "-5c15-555e-a1c8-6166a78fc808")))) + +(deftest b64-hash-test + (testing "Testing the base64 encoding of a hash." + (is (= (b64-hash [1 2 3 {:key 5 :value 10}]) + "TREJlRrK211AASiqQMFG9RLFW0CPC/arrCxeaUj27Qho2USJU40T01uCdjUg/OMiPGttyL1ELPCrVXXhMIroRQ==")))) + + +(deftest test-md5 + (is (= (hex/encode (md5/str->md5 "geheimnis")) + "525e92c6aa11544a2ab794f8921ecb0f"))) + +#_(run-tests) + +#?(:cljs + (deftest utf8-test + (is (= (js->clj (utf8 "小鳩ちゃんかわいいなぁ")) + [229 176 143 233 179 169 227 129 161 227 130 131 227 130 147 227 + 129 139 227 130 143 227 129 132 227 129 132 227 129 170 227 129 129])))) + +#?(:cljs + (defn ^:export run + [] + (enable-console-print!) + (run-tests))) + +;; fire up repl +#_(do + (ns dev) + (def repl-env (reset! cemerick.austin.repls/browser-repl-env + (cemerick.austin/repl-env))) + (cemerick.austin.repls/cljs-repl repl-env)) + diff --git a/test-resources/lib_tests/omniconf/core_test.clj b/test-resources/lib_tests/omniconf/core_test.clj new file mode 100644 index 00000000..2f832f56 --- /dev/null +++ b/test-resources/lib_tests/omniconf/core_test.clj @@ -0,0 +1,36 @@ +(ns omniconf.core-test + (:require [clojure.string :as str] + [clojure.test :refer [deftest is testing]] + [omniconf.core :as cfg]) + (:import (java.io File))) + +(cfg/define + {:conf {:type :file} + :foo {:type :string + :required true} + :the-boolean {:type :boolean} + :missing {:type :string + :required true}}) + +(deftest load-cfg-test + (testing "multiple config sources" + (let [temp-cfg-file (File/createTempFile "cfg" "edn") + _ (.deleteOnExit temp-cfg-file) + fake-args ["--conf" (.getAbsolutePath temp-cfg-file) + "--foo" "this will be overridden"]] + (do + ; put some props in the temp file + (spit temp-cfg-file "{:foo \"final value\" :the-boolean false }") + ; and set a system property + (System/setProperty "the-boolean" "18") + (cfg/populate-from-cmd fake-args) + (cfg/populate-from-file (cfg/get :conf)) + (cfg/populate-from-properties) + ; cleanup + (System/clearProperty "the-boolean"))) + (is (= "final value" (cfg/get :foo))) + (is (= true (cfg/get :the-boolean))) + (is (thrown-with-msg? Exception #":missing" (cfg/verify))) + (cfg/populate-from-map {:missing "abc"}) + (let [verify-output (with-out-str (cfg/verify))] + (is (every? #(str/includes? verify-output %) [":missing" "abc"]))))) diff --git a/test-resources/lib_tests/selmer/core_test.clj b/test-resources/lib_tests/selmer/core_test.clj index ddf65652..a2f35782 100644 --- a/test-resources/lib_tests/selmer/core_test.clj +++ b/test-resources/lib_tests/selmer/core_test.clj @@ -1048,7 +1048,7 @@ ;; (is (nil? *custom-resource-path*))) (deftest custom-resource-path-setting-url - (p/set-resource-path! (clojure.java.io/resource "templates/inheritance")) + (p/set-resource-path! "templates/inheritance") #_(is (string? *custom-resource-path*)) (is (= (fix-line-sep "Hello, World!\n") (render-file "foo.html" {:name "World"}))) (p/set-resource-path! nil)) @@ -1257,4 +1257,3 @@ "debug-value")) (testing "basic rendering escapes HTML" (is (str/includes? (basic-edn->html {:a "
"}) """))))
-
diff --git a/test-resources/pod.clj b/test-resources/pod.clj
index a92baecf..cd57ea17 100644
--- a/test-resources/pod.clj
+++ b/test-resources/pod.clj
@@ -128,10 +128,12 @@
   (if (contains? cli-args "--run-as-pod")
     (do (debug "running pod with cli args" cli-args)
         (run-pod cli-args))
-    (let [native? (contains? cli-args "--native")]
-      (pods/load-pod (if native?
-                       (into ["./bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)
-                       (into ["lein" "bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)))
+    (let [native? (contains? cli-args "--native")
+          windows? (contains? cli-args "--windows")]
+      (pods/load-pod (cond
+                       native?  (into ["./bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)
+                       windows? (into ["cmd" "/c" "lein" "bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)
+                       :else    (into ["lein" "bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)))
       (require '[pod.test-pod])
       (if (contains? cli-args "--json")
         (do
diff --git a/test/babashka/async_test.clj b/test/babashka/async_test.clj
index 783e7788..f2be1a35 100644
--- a/test/babashka/async_test.clj
+++ b/test/babashka/async_test.clj
@@ -4,7 +4,7 @@
    [clojure.edn :as edn]
    [clojure.test :as t :refer [deftest is]]))
 
-(deftest alts!!-test
+(deftest ^:skip-windows alts!!-test
   (is (= "process 2\n" (test-utils/bb nil "
    (defn async-command [& args]
      (async/thread (apply shell/sh \"bash\" \"-c\" args)))
diff --git a/test/babashka/bb_edn_test.clj b/test/babashka/bb_edn_test.clj
index 6c43a6ea..e54f7862 100644
--- a/test/babashka/bb_edn_test.clj
+++ b/test/babashka/bb_edn_test.clj
@@ -37,54 +37,66 @@
 (deftest task-test
   (test-utils/with-config '{:tasks {foo (+ 1 2 3)}}
     (is (= 6 (bb "run" "--prn" "foo"))))
-  (let [tmp-dir (fs/create-temp-dir)
-        out     (str (fs/file tmp-dir "out.txt"))]
+  (testing "init test"
+    (test-utils/with-config '{:tasks {:init (def x 1)
+                                      foo   x}}
+      (is (= 1 (bb "run" "--prn" "foo")))))
+  (testing "requires test"
+    (test-utils/with-config '{:tasks {:requires ([babashka.fs :as fs])
+                                      foo       (fs/exists? ".")}}
+      (is (= true (bb "run" "--prn" "foo"))))
+    (test-utils/with-config '{:tasks {foo {:requires ([babashka.fs :as fs])
+                                           :task     (fs/exists? ".")}}}
+      (is (= true (bb "run" "--prn" "foo"))))
+    (test-utils/with-config '{:tasks {bar {:requires ([babashka.fs :as fs])}
+                                      foo {:depends [bar]
+                                           :task    (fs/exists? ".")}}}
+      (is (= true (bb "run" "--prn" "foo")))))
+  (testing "map returned from task"
+    (test-utils/with-config '{:tasks {foo {:task {:a 1 :b 2}}}}
+      (is (= {:a 1 :b 2} (bb "run" "--prn" "foo")))))
+  (let [tmp-dir  (fs/create-temp-dir)
+        out      (str (fs/file tmp-dir "out.txt"))
+        echo-cmd (if main/windows? "cmd /c echo" "echo")
+        ls-cmd   (if main/windows? "cmd /c dir" "ls")
+        fix-lines test-utils/normalize]
     (testing "shell test"
       (test-utils/with-config {:tasks {'foo (list 'shell {:out out}
-                                                  "echo hello")}}
+                                              echo-cmd "hello")}}
         (bb "foo")
-        (is (= "hello\n" (slurp out)))))
-    (fs/delete out)
-    (testing "shell test with :continue"
-      (test-utils/with-config {:tasks {'foo (list 'shell {:out      out
-                                                          :err      out
-                                                          :continue true}
-                                                  "ls foobar")}}
-        (bb "foo")
-        (is (str/includes? (slurp out)
-                           "foobar"))))
+        (is (= "hello\n" (fix-lines (slurp out))))))
     (fs/delete out)
     (testing "shell test with :continue fn"
       (test-utils/with-config {:tasks {'foo (list '-> (list 'shell {:out      out
                                                                     :err      out
                                                                     :continue '(fn [proc]
                                                                                  (contains? proc :exit))}
-                                                            "ls foobar")
-                                                  :exit)}}
+                                                        ls-cmd "foobar")
+                                              :exit)}}
         (is (pos? (bb "run" "--prn" "foo")))))
     (testing "shell test with :error"
       (test-utils/with-config
         {:tasks {'foo (list '-> (list 'shell {:out      out
                                               :err      out
-                                              :error-fn '(constantly 1337) }
-                                      "ls foobar"))}}
+                                              :error-fn '(constantly 1337)}
+                                  ls-cmd "foobar"))}}
         (is (= 1337 (bb "run" "--prn" "foo"))))
       (test-utils/with-config
-        {:tasks {'foo (list '-> (list 'shell {:out      out
-                                              :err      out
+        {:tasks {'foo (list '-> (list 'shell {:out out
+                                              :err out
                                               :error-fn
-                                              '(fn [opts]
-                                                 (and (:task opts)
-                                                      (:proc opts)
-                                                      (not (zero? (:exit (:proc opts))))))}
-                                      "ls foobar"))}}
+                                                   '(fn [opts]
+                                                      (and (:task opts)
+                                                        (:proc opts)
+                                                        (not (zero? (:exit (:proc opts))))))}
+                                  ls-cmd "foobar"))}}
         (is (true? (bb "run" "--prn" "foo")))))
     (fs/delete out)
     (testing "clojure test"
       (test-utils/with-config {:tasks {'foo (list 'clojure {:out out}
-                                                  "-M -e" "(println :yolo)")}}
+                                              "-M -e" "(println :yolo)")}}
         (bb "foo")
-        (is (= ":yolo\n" (slurp out)))))
+        (is (= ":yolo\n" (fix-lines (slurp out))))))
     (fs/delete out)
     (testing "depends"
       (test-utils/with-config {:tasks {'quux (list 'spit out "quux\n")
@@ -107,24 +119,6 @@
                                                 :task    (list 'spit out "foo\n" :append true)}}}
           (bb "foo")
           (is (= "quux\nbaz\nbar\nfoo\n" (slurp out))))))
-  (testing "init test"
-    (test-utils/with-config '{:tasks {:init (def x 1)
-                                      foo   x}}
-      (is (= 1 (bb "run" "--prn" "foo")))))
-  (testing "requires test"
-    (test-utils/with-config '{:tasks {:requires ([babashka.fs :as fs])
-                                      foo       (fs/exists? ".")}}
-      (is (= true (bb "run" "--prn" "foo"))))
-    (test-utils/with-config '{:tasks {foo {:requires ([babashka.fs :as fs])
-                                           :task     (fs/exists? ".")}}}
-      (is (= true (bb "run" "--prn" "foo"))))
-    (test-utils/with-config '{:tasks {bar {:requires ([babashka.fs :as fs])}
-                                      foo {:depends [bar]
-                                           :task    (fs/exists? ".")}}}
-      (is (= true (bb "run" "--prn" "foo")))))
-  (testing "map returned from task"
-    (test-utils/with-config '{:tasks {foo {:task {:a 1 :b 2}}}}
-      (is (= {:a 1 :b 2} (bb "run" "--prn" "foo")))))
   (testing "fully qualified symbol execution"
     (test-utils/with-config {:paths ["test-resources/task_scripts"]
                              :tasks '{foo tasks/foo}}
@@ -167,27 +161,27 @@
   (testing "no such task"
     (test-utils/with-config '{:tasks {a (+ 1 2 3)}}
       (is (thrown-with-msg?
-           Exception #"No such task: b"
-           (bb "run" "b")))))
+            Exception #"No such task: b"
+            (bb "run" "b")))))
   (testing "unresolved dependency"
     (test-utils/with-config '{:tasks {a (+ 1 2 3)
                                       b {:depends [x]
                                          :task    (+ a 4 5 6)}}}
       (is (thrown-with-msg?
-           Exception #"No such task: x"
-           (bb "run" "b")))))
+            Exception #"No such task: x"
+            (bb "run" "b")))))
   (testing "cyclic task"
     (test-utils/with-config '{:tasks {b {:depends [b]
                                          :task    (+ a 4 5 6)}}}
       (is (thrown-with-msg?
-           Exception #"Cyclic task: b"
-           (bb "run" "b"))))
+            Exception #"Cyclic task: b"
+            (bb "run" "b"))))
     (test-utils/with-config '{:tasks {c {:depends [b]}
                                       b {:depends [c]
                                          :task    (+ a 4 5 6)}}}
       (is (thrown-with-msg?
-           Exception #"Cyclic task: b"
-           (bb "run" "b")))))
+            Exception #"Cyclic task: b"
+            (bb "run" "b")))))
   (testing "doc"
     (test-utils/with-config '{:tasks {b {:doc "Beautiful docstring"}}}
       (let [s (test-utils/bb nil "doc" "b")]
@@ -196,25 +190,18 @@
     (test-utils/with-config '{:tasks {b (System/getProperty "babashka.task")}}
       (let [s (bb "run" "--prn" "b")]
         (is (= "b" s)))))
-  (testing "shell pipe test"
-    (test-utils/with-config '{:tasks {a (-> (shell {:out :string}
-                                                   "echo hello")
-                                            (shell {:out :string} "cat")
-                                            :out)}}
-      (let [s (bb "run" "--prn" "a")]
-        (is (= "hello\n" s)))))
   (testing "parallel test"
     (test-utils/with-config (edn/read-string (slurp "test-resources/coffee-tasks.edn"))
-      (let [tree [:made-coffee [[:ground-beans [:measured-beans]] [:heated-water [:poured-water]] :filter :mug]]
-            t0 (System/currentTimeMillis)
-            s (bb "run" "--prn" "coffeep")
-            t1 (System/currentTimeMillis)
+      (let [tree             [:made-coffee [[:ground-beans [:measured-beans]] [:heated-water [:poured-water]] :filter :mug]]
+            t0               (System/currentTimeMillis)
+            s                (bb "run" "--prn" "coffeep")
+            t1               (System/currentTimeMillis)
             delta-sequential (- t1 t0)]
         (is (= tree s))
         (test-utils/with-config (edn/read-string (slurp "test-resources/coffee-tasks.edn"))
-          (let [t0 (System/currentTimeMillis)
-                s (bb "run" "--parallel" "--prn" "coffeep")
-                t1 (System/currentTimeMillis)
+          (let [t0             (System/currentTimeMillis)
+                s              (bb "run" "--parallel" "--prn" "coffeep")
+                t1             (System/currentTimeMillis)
                 delta-parallel (- t1 t0)]
             (is (= tree s))
             (is (< delta-parallel delta-sequential))))))
@@ -224,40 +211,76 @@
                                               (throw (ex-info "0 noes" {})))
                                         c {:depends [a b]}}}
         (is (thrown-with-msg? Exception #"0 noes"
-                              (bb "run" "--parallel" "c")))))
+              (bb "run" "--parallel" "c")))))
     (testing "edge case"
       (test-utils/with-config '{:tasks
                                 {a   (run '-a {:parallel true})
                                  -a  {:depends [a:a a:b c]
-                                      :task (prn [a:a a:b c])}
+                                      :task    (prn [a:a a:b c])}
                                  a:a {:depends [c]
-                                      :task (+ 1 2 3)}
+                                      :task    (+ 1 2 3)}
                                  a:b {:depends [c]
-                                      :task (do (Thread/sleep 10)
-                                                (+ 1 2 3))}
-                                 c (do (Thread/sleep 10) :c)}}
+                                      :task    (do (Thread/sleep 10)
+                                                   (+ 1 2 3))}
+                                 c   (do (Thread/sleep 10) :c)}}
         (is (= [6 6 :c] (bb "run" "--prn" "a"))))))
   (testing "dynamic vars"
     (test-utils/with-config '{:tasks
                               {:init (def ^:dynamic *foo* true)
-                               a (do
-                                   (def ^:dynamic *bar* false)
-                                   (binding [*foo* false
-                                             *bar* true]
-                                     [*foo* *bar*]))}}
+                               a     (do
+                                       (def ^:dynamic *bar* false)
+                                       (binding [*foo* false
+                                                 *bar* true]
+                                         [*foo* *bar*]))}}
       (is (= [false true] (bb "run" "--prn" "a")))))
   (testing "stable namespace name"
     (test-utils/with-config '{:tasks
-                              {:init (do (def ^:dynamic *jdk*)
-                                         (def ^:dynamic *server*))
-                               server [*jdk* *server*]
-                               run-all (for [jdk [8 11 15]
+                              {:init   (do (def ^:dynamic *jdk*)
+                                           (def ^:dynamic *server*))
+                               server  [*jdk* *server*]
+                               run-all (for [jdk    [8 11 15]
                                              server [:foo :bar]]
-                                         (binding [*jdk* jdk
+                                         (binding [*jdk*    jdk
                                                    *server* server]
                                            (babashka.tasks/run 'server)))}}
       (is (= '([8 :foo] [8 :bar] [11 :foo] [11 :bar] [15 :foo] [15 :bar])
-             (bb "run" "--prn" "run-all"))))))
+            (bb "run" "--prn" "run-all")))))
+  (let [tmp-dir (fs/create-temp-dir)
+        out     (str (fs/file tmp-dir "out.txt"))
+        ls-cmd  (if main/windows? "cmd /c dir" "ls")
+        expected-output (if main/windows? "File Not Found" "foobar")]
+    (testing "shell test with :continue"
+      (test-utils/with-config {:tasks {'foo (list 'shell {:out      out
+                                                          :err      out
+                                                          :continue true}
+                                              (str ls-cmd " foobar"))}}
+        (bb "foo")
+        (is (str/includes? (slurp out)
+              expected-output))))
+    (fs/delete out)))
+
+
+(deftest ^:skip-windows unix-task-test
+  (testing "shell pipe test"
+    (test-utils/with-config '{:tasks {a (-> (shell {:out :string}
+                                              "echo hello")
+                                          (shell {:out :string} "cat")
+                                          :out)}}
+      (let [s (bb "run" "--prn" "a")]
+        (is (= "hello\n" s))))))
+
+(deftest ^:windows-only win-task-test
+  (when main/windows?
+    (testing "shell pipe test"
+      ; this task prints the contents of deps.edn
+      (test-utils/with-config '{:tasks {a (->> (shell {:out :string}
+                                                 "cmd /c echo deps.edn")
+                                            :out
+                                            clojure.string/trim-newline
+                                            (shell {:out :string} "cmd /c type")
+                                            :out)}}
+        (let [s (bb "run" "--prn" "a")]
+          (is (str/includes? s "paths")))))))
 
 (deftest list-tasks-test
   (test-utils/with-config {}
diff --git a/test/babashka/classpath_test.clj b/test/babashka/classpath_test.clj
index de8cfab3..ae04e945 100644
--- a/test/babashka/classpath_test.clj
+++ b/test/babashka/classpath_test.clj
@@ -9,6 +9,8 @@
 (defn bb [input & args]
   (edn/read-string (apply tu/bb (when (some? input) (str input)) (map str args))))
 
+(def path-sep (System/getProperty "path.separator"))
+
 (deftest classpath-test
   (is (= :my-script/bb
          (bb nil "--classpath" "test-resources/babashka/src_for_classpath_test"
@@ -24,12 +26,12 @@
   (is (= "test-resources"
          (bb nil "--classpath" "test-resources"
              "(require '[babashka.classpath :as cp]) (cp/get-classpath)")))
-  (is (= "test-resources:foobar"
-         (bb nil "--classpath" "test-resources"
-             "(require '[babashka.classpath :as cp]) (cp/add-classpath \"foobar\") (cp/get-classpath)")))
+  (is (= (str/join path-sep ["test-resources" "foobar"])
+        (bb nil "--classpath" "test-resources"
+            "(require '[babashka.classpath :as cp]) (cp/add-classpath \"foobar\") (cp/get-classpath)")))
   (is (= ["foo" "bar"]
-         (bb nil "--classpath" "foo:bar"
-             "(require '[babashka.classpath :as cp]) (cp/split-classpath (cp/get-classpath))"))))
+         (bb nil "--classpath" (str/join path-sep ["foo" "bar"])
+           "(require '[babashka.classpath :as cp]) (cp/split-classpath (cp/get-classpath))"))))
 
 (deftest classpath-env-test
   ;; for this test you have to set `BABASHKA_CLASSPATH` to test-resources/babashka/src_for_classpath_test/env
@@ -57,7 +59,8 @@
 (deftest resource-test
   (let [tmp-file (java.io.File/createTempFile "icon" ".png")]
     (.deleteOnExit tmp-file)
-    (bb nil "--classpath" "logo" "-e" (format "(io/copy (io/input-stream (io/resource \"icon.png\")) (io/file \"%s\"))" (.getPath tmp-file)))
+    (bb nil "--classpath" "logo" "-e" (format "(io/copy (io/input-stream (io/resource \"icon.png\")) (io/file \"%s\"))"
+                                        (tu/escape-file-paths (.getPath tmp-file))))
     (is (= (.length (io/file "logo" "icon.png"))
            (.length tmp-file))))
   (testing "No exception on absolute path"
diff --git a/test/babashka/deps_test.clj b/test/babashka/deps_test.clj
index c7c5d448..01ff96e5 100644
--- a/test/babashka/deps_test.clj
+++ b/test/babashka/deps_test.clj
@@ -1,5 +1,6 @@
 (ns babashka.deps-test
   (:require
+   [babashka.fs :as fs]
    [babashka.test-utils :as test-utils]
    [clojure.edn :as edn]
    [clojure.test :as test :refer [deftest is testing]]))
@@ -10,7 +11,8 @@
     :eof nil}
    (apply test-utils/bb nil (map str args))))
 
-(deftest dependency-test (is (= #{:a :c :b} (bb "
+(deftest dependency-test
+  (is (= #{:a :c :b} (bb "
 (require '[babashka.deps :as deps])
 
 (deps/add-deps '{:deps {com.stuartsierra/dependency {:mvn/version \"1.0.0\"}}})
@@ -24,7 +26,19 @@
             (dep/depend :d :c)))
 
 (dep/transitive-dependencies g1 :d)
-"))))
+")))
+  (testing "GITLIBS can set location of .gitlibs dir"
+    (let [tmp-dir (fs/create-temp-dir)
+          libs-dir (fs/file tmp-dir ".gitlibs")
+          libs-dir2 (fs/file tmp-dir ".gitlibs2")]
+      (bb (pr-str `(do (babashka.deps/add-deps '{:deps {babashka/process {:git/url "https://github.com/babashka/process" :sha "4c6699d06b49773d3e5c5b4c11d3334fb78cc996"}}}
+                                               {:force true
+                                                :env {"GITLIBS" ~(str libs-dir)}}) nil)))
+      (bb (pr-str `(do (babashka.deps/add-deps '{:deps {babashka/process {:git/url "https://github.com/babashka/process" :sha "4c6699d06b49773d3e5c5b4c11d3334fb78cc996"}}}
+                                               {:force true
+                                                :extra-env {"GITLIBS" ~(str libs-dir2)}}) nil)))
+      (is (fs/exists? libs-dir))
+      (is (fs/exists? libs-dir2)))))
 
 (deftest clojure-test
   (testing "-Stree prints to *out*"
@@ -42,18 +56,30 @@
 (babashka.deps/clojure [\"-P\"])
 true
 "))))
-  (is (= "6\n" (bb "
+  (is (= "6\n" (test-utils/normalize (bb "
 (require '[babashka.deps :as deps])
 (require '[babashka.process :as p])
 
 (-> (babashka.deps/clojure [\"-M\" \"-e\" \"(+ 1 2 3)\"] {:out :string})
     (p/check)
     :out)
-")))
+"))))
   (when-not test-utils/native?
     (is (thrown-with-msg? Exception #"Option changed" (bb "
 (require '[babashka.deps :as deps])
 (babashka.deps/clojure [\"-Sresolve-tags\"])
 "))))
   (is (true? (bb "
-(= 5 (:exit @(babashka.deps/clojure [] {:in \"(System/exit 5)\" :out :string})))"))))
+(= 5 (:exit @(babashka.deps/clojure [] {:in \"(System/exit 5)\" :out :string})))")))
+  (testing "start from other directory"
+    (is (= {1 {:id 1}, 2 {:id 2}}
+           (edn/read-string (bb "
+(:out @(babashka.deps/clojure [\"-M\" \"-e\" \"(require 'medley.core) (medley.core/index-by :id [{:id 1} {:id 2}])\"] {:out :string :dir \"test-resources/clojure-dir-test\"}))")))))
+  (testing "GITLIBS can set location of .gitlibs dir"
+    (let [tmp-dir (fs/create-temp-dir)
+          libs-dir (fs/file tmp-dir ".gitlibs")
+          libs-dir2 (fs/file tmp-dir ".gitlibs2")]
+      (bb (pr-str `(do (babashka.deps/clojure ["-Sforce" "-Spath" "-Sdeps" "{:deps {babashka/process {:git/url \"https://github.com/babashka/process\" :sha \"4c6699d06b49773d3e5c5b4c11d3334fb78cc996\"}}}"] {:out :string :env {"GITLIBS" ~(str libs-dir)}}) nil)))
+      (bb (pr-str `(do (babashka.deps/clojure ["-Sforce" "-Spath" "-Sdeps" "{:deps {babashka/process {:git/url \"https://github.com/babashka/process\" :sha \"4c6699d06b49773d3e5c5b4c11d3334fb78cc996\"}}}"] {:out :string :extra-env {"GITLIBS" ~(str libs-dir2)}}) nil)))
+      (is (fs/exists? libs-dir))
+      (is (fs/exists? libs-dir2)))))
diff --git a/test/babashka/error_test.clj b/test/babashka/error_test.clj
index 25914e35..064290ff 100644
--- a/test/babashka/error_test.clj
+++ b/test/babashka/error_test.clj
@@ -40,7 +40,7 @@
 (defn foo [] (/ 1 0))
 (foo)")
                     (catch Exception e (ex-message e)))]
-    (is (str/includes? output "----- Stack trace --------------------------------------------------------------
+    (is (str/includes? (tu/normalize output) "----- Stack trace --------------------------------------------------------------
 clojure.core// - 
 user/foo       - :2:14
 user/foo       - :2:1
@@ -51,7 +51,7 @@ user           - :3:1"))))
 (defn foo [] (/ 1 0))
 (foo)")
                     (catch Exception e (ex-message e)))]
-    (is (str/includes? output "----- Context ------------------------------------------------------------------
+    (is (str/includes? (tu/normalize output) "----- Context ------------------------------------------------------------------
 1: 
 2: (defn foo [] (/ 1 0))
                 ^--- Divide by zero
@@ -60,14 +60,14 @@ user           - :3:1"))))
 (deftest parse-error-context-test
   (let [output (try (tu/bb nil "{:a}")
                     (catch Exception e (ex-message e)))]
-    (is (str/includes? output "----- Context ------------------------------------------------------------------
+    (is (str/includes? (tu/normalize output) "----- Context ------------------------------------------------------------------
 1: {:a}
    ^--- The map literal starting with :a contains 1 form(s)."))))
 
 (deftest jar-error-test
   (let [output (try (tu/bb nil "-cp" (.getPath (io/file "test-resources" "divide_by_zero.jar")) "-e" "(require 'foo)")
                     (catch Exception e (ex-message e)))]
-    (is (str/includes? output "----- Error --------------------------------------------------------------------
+    (is (str/includes? (tu/normalize output) "----- Error --------------------------------------------------------------------
 Type:     java.lang.ArithmeticException
 Message:  Divide by zero
 Location: foo.clj:1:10
@@ -83,7 +83,7 @@ foo            - foo.clj:1:10"))))
 (deftest static-call-test
   (let [output (try (tu/bb nil "-e" "File/x")
                     (catch Exception e (ex-message e)))]
-    (is (str/includes? output
+    (is (str/includes? (tu/normalize output)
                        "----- Error --------------------------------------------------------------------
 Type:     java.lang.IllegalArgumentException
 Message:  No matching field found: x for class java.io.File
@@ -97,7 +97,7 @@ Location: :1:1
 user - :1:1"))
     (let [output (try (tu/bb nil "-e" "(File/x)")
                       (catch Exception e (ex-message e)))]
-      (is (str/includes? output
+      (is (str/includes? (tu/normalize output)
                          "----- Error --------------------------------------------------------------------
 Type:     java.lang.IllegalArgumentException
 Message:  No matching method x found taking 0 args
diff --git a/test/babashka/impl/clojure/java/shell_test.clj b/test/babashka/impl/clojure/java/shell_test.clj
index 0f1cd8db..3a9cc45a 100644
--- a/test/babashka/impl/clojure/java/shell_test.clj
+++ b/test/babashka/impl/clojure/java/shell_test.clj
@@ -1,9 +1,11 @@
 (ns babashka.impl.clojure.java.shell-test
-  (:require [clojure.test :as t :refer [deftest is testing]]
+  (:require [babashka.main :as main]
             [babashka.test-utils :as test-utils]
-            [clojure.string :as str]))
+            [clojure.string :as str]
+            [clojure.test :as t :refer [deftest is testing]]))
 
-(deftest with-sh-env-test
+
+(deftest ^:skip-windows with-sh-env-test
   (is (= "\"BAR\""
          (str/trim (test-utils/bb nil "
 (-> (shell/with-sh-env {:FOO \"BAR\"}
@@ -15,3 +17,17 @@
       (shell/sh \"ls\"))
     :out)"))
                      "icon.svg")))
+
+(deftest ^:windows-only win-with-sh-env-test
+  (when main/windows?
+    (is (= "\"BAR\""
+          (str/trim (test-utils/bb nil "
+(-> (shell/with-sh-env {:FOO \"BAR\"}
+      (shell/sh \"cmd\" \"/c\" \"echo %FOO%\"))
+    :out
+    str/trim)"))))
+    (is (str/includes? (str/trim (test-utils/bb nil "
+(-> (shell/with-sh-dir \"logo\"
+      (shell/sh \"cmd\" \"/c\" \"dir\"))
+    :out)"))
+          "icon.svg"))))
diff --git a/test/babashka/impl/nrepl_server_test.clj b/test/babashka/impl/nrepl_server_test.clj
index 822fb802..3367e89e 100644
--- a/test/babashka/impl/nrepl_server_test.clj
+++ b/test/babashka/impl/nrepl_server_test.clj
@@ -182,7 +182,7 @@
           (let [reply (read-reply in session @id)]
             (is (= "Hello\n" (tu/normalize (:out reply))))))))))
 
-(deftest nrepl-server-test
+(deftest ^:skip-windows nrepl-server-test
   (let [proc-state (atom nil)
         server-state (atom nil)]
     (try
diff --git a/test/babashka/impl/repl_test.clj b/test/babashka/impl/repl_test.clj
index a1bd730b..c82b12ca 100644
--- a/test/babashka/impl/repl_test.clj
+++ b/test/babashka/impl/repl_test.clj
@@ -47,6 +47,8 @@
   (assert-repl "1\n(dec *1)(+ *2 *2)" "2")
   (assert-repl "1\n(dec *1)(+ *2 *2)" "2")
   (assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]")
+  (assert-repl "(read-line)hello" "hello")
+  (assert-repl "(read-line)\nhello" "hello")
   (assert-repl-error "(+ 1 nil)" "NullPointerException")
   (assert-repl-error "(/ 1 0) (pst 1)" "Divide by zero\n\tclojure.lang.Numbers"))
 
diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj
index a56bfc87..8854383d 100644
--- a/test/babashka/main_test.clj
+++ b/test/babashka/main_test.clj
@@ -12,12 +12,13 @@
    [sci.core :as sci]))
 
 (defn bb [input & args]
-  (edn/read-string
-   {:readers *data-readers*
-    :eof nil}
-   (apply test-utils/bb (when (some? input) (str input)) (map str args))))
+  (test-utils/normalize
+   (edn/read-string
+    {:readers *data-readers*
+     :eof nil}
+    (apply test-utils/bb (when (some? input) (str input)) (map str args)))))
 
-(deftest ^:windows parse-opts-test
+(deftest parse-opts-test
   (is (= "1667"
          (:nrepl (main/parse-opts ["--nrepl-server"]))))
   (is (= "1666"
@@ -52,17 +53,17 @@
   (is (= '("-e" "1") (bb nil "-e" "*command-line-args*" "--" "-e" "1")))
   (let [v (bb nil "--describe")]
     (is (:babashka/version v))
-    (is (:feature/xml v))))
+    (is (:feature/xml v)))
+  (is (= {:force? true} (main/parse-opts ["--force"]))))
 
-
-(deftest ^:windows version-test
+(deftest version-test
   (is (= [1 0 0] (main/parse-version "1.0.0-SNAPSHOT")))
   (is (main/satisfies-min-version? "0.1.0"))
   (is (main/satisfies-min-version? "0.1.0-SNAPSHOT"))
   (is (not (main/satisfies-min-version? "300.0.0")))
   (is (not (main/satisfies-min-version? "300.0.0-SNAPSHOT"))))
 
-(deftest ^:windows print-error-test
+(deftest print-error-test
   (is (thrown-with-msg? Exception #"java.lang.NullPointerException"
                         (bb nil "(subs nil 0 0)"))))
 
@@ -120,7 +121,7 @@
     (is (= "localhost" (bb "#ordered/map ([:test \"localhost\"])"
                            "(:test *input*)"))))
   (testing "bb doesn't wait for input if *input* isn't used"
-    (is (= "2\n" (with-out-str (main/main "(inc 1)"))))))
+    (is (= "2\n" (test-utils/normalize (with-out-str (main/main "(inc 1)")))))))
 
 (deftest println-test
   (is (= "hello\n" (test-utils/bb nil "(println \"hello\")"))))
@@ -131,7 +132,7 @@
     (doseq [s res]
       (is (not-empty s)))))
 
-(deftest ^:windows malformed-command-line-args-test
+(deftest malformed-command-line-args-test
   (is (thrown-with-msg? Exception #"File does not exist: non-existing"
                         (bb nil "-f" "non-existing"))))
 
@@ -152,11 +153,11 @@
     (.deleteOnExit tmp)
     (spit tmp "(ns foo) (defn foo [x y] (+ x y)) (defn bar [x y] (* x y))")
     (is (= "120\n" (test-utils/bb nil (format "(load-file \"%s\") (foo/bar (foo/foo 10 30) 3)"
-                                              (.getPath tmp)))))
+                                              (test-utils/escape-file-paths (.getPath tmp))))))
     (testing "namespace is restored after load file"
       (is (= 'start-ns
              (bb nil (format "(ns start-ns) (load-file \"%s\") (ns-name *ns*)"
-                             (.getPath tmp))))))))
+                             (test-utils/escape-file-paths (.getPath tmp)))))))))
 
 (deftest repl-source-test
   (let [tmp (java.io.File/createTempFile "lib" ".clj")
@@ -174,15 +175,14 @@
 (load-file \"%s\")
 (require '[clojure.repl :refer [source]])
 (with-out-str (source %s/foo))"
-                             (.getPath tmp)
+                             (test-utils/escape-file-paths (.getPath tmp))
                              name)))))
     (testing "print source from file on classpath"
       (is (= "(defn foo [x y]\n  (+ x y))\n"
-             (test-utils/normalize
-              (bb nil
-                  "-cp" dir
-                  "-e" (format "(require '[clojure.repl :refer [source]] '[%s])" name)
-                  "-e" (format "(with-out-str (source %s/foo))" name))))))))
+             (bb nil
+                 "-cp" dir
+                 "-e" (format "(require '[clojure.repl :refer [source]] '[%s])" name)
+                 "-e" (format "(with-out-str (source %s/foo))" name)))))))
 
 (deftest eval-test
   (is (= "120\n" (test-utils/bb nil "(eval '(do (defn foo [x y] (+ x y))
@@ -199,7 +199,8 @@
   (is (true? (bb nil "(.exists (io/file \"README.md\"))")))
   (is (true? (bb nil "(.canWrite (io/file \"README.md\"))"))))
 
-(deftest pipe-test
+; skipped because the windows shell doesn't seem to deal well with infinite things
+(deftest ^:skip-windows pipe-test
   (when (and test-utils/native?
              (not main/windows?))
     (let [out (:out (sh "bash" "-c" "./bb -o '(range)' |
@@ -207,16 +208,22 @@
                          head -n10"))
           out (str/split-lines out)
           out (map edn/read-string out)]
-      (is (= (take 10 (map #(* % %) (range))) out))))
-  (when (and test-utils/native?
-             (not main/windows?))
+      (is (= (take 10 (map #(* % %) (range))) out)))
     (let [out (:out (sh "bash" "-c" "./bb -O '(repeat \"dude\")' |
                          ./bb --stream '(str *input* \"rino\")' |
                          ./bb -I '(take 3 *input*)'"))
           out (edn/read-string out)]
       (is (= '("duderino" "duderino" "duderino") out)))))
 
-(deftest lazy-text-in-test
+(deftest ^:windows-only win-pipe-test
+  (when (and test-utils/native? main/windows?)
+    (let [out (:out (sh "cmd" "/c" ".\\bb -O \"(repeat 50 \\\"dude\\\")\" |"
+                         ".\\bb --stream \"(str *input* \\\"rino\\\")\" |"
+                         ".\\bb -I \"(take 3 *input*)\""))
+          out (edn/read-string out)]
+      (is (= '("duderino" "duderino" "duderino") out)))))
+
+(deftest ^:skip-windows lazy-text-in-test
   (when test-utils/native?
     (let [out (:out (sh "bash" "-c" "yes | ./bb -i '(take 2 *input*)'"))
           out (edn/read-string out)]
@@ -232,8 +239,11 @@
                         @x)"))))
 
 (deftest process-builder-test
-  (is (str/includes? (bb nil "
-(def pb (ProcessBuilder. [\"ls\"]))
+  (let [cmd-line (if main/windows?
+                   "[\"cmd\" \"/c\" \"dir\" ]"
+                   "[\"ls\"]")]
+    (is (str/includes? (bb nil (str "
+(def pb (ProcessBuilder. " cmd-line "))
 (def env (.environment pb))
 (.put env \"FOO\" \"BAR\") ;; test for issue 460
 (def ls (-> pb (.start)))
@@ -241,24 +251,21 @@
 (.write (io/writer input) \"hello\") ;; dummy test just to see if this works
 (def output (.getInputStream ls))
 (assert (int? (.waitFor ls)))
-(slurp output)")
-                     "LICENSE"))
+(slurp output)"))
+                       "LICENSE")))
   (testing "bb is able to kill subprocesses created by ProcessBuilder"
     (when test-utils/native?
-      (let [output (test-utils/bb nil (io/file "test" "babashka" "scripts" "kill_child_processes.bb"))
+      (let [process-count (if main/windows? 6 3)
+            output (test-utils/bb nil (io/file "test" "babashka" "scripts" "kill_child_processes.bb"))
             parsed (edn/read-string (format "[%s]" output))]
         (is (every? number? parsed))
-        (is (= 3 (count parsed)))))))
+        (is (= process-count (count parsed)))))))
 
 (deftest create-temp-file-test
-  (let [temp-dir-path (System/getProperty "java.io.tmpdir")]
-    (is (= true
-           (bb nil (format "(let [tdir (io/file \"%s\")
-                                 tfile
-                                 (File/createTempFile \"ctf\" \"tmp\" tdir)]
+  (is (= true
+        (bb nil "(let [tfile (File/createTempFile \"ctf\" \"tmp\")]
                              (.deleteOnExit tfile) ; for cleanup
-                             (.exists tfile))"
-                           temp-dir-path))))))
+                             (.exists tfile))"))))
 
 (deftest wait-for-port-test
   (let [server (test-utils/start-server! 1777)]
@@ -270,7 +277,7 @@
     (is (=  1777 (:port edn)))
     (is (number? (:took edn)))))
 
-(deftest wait-for-path-test
+(deftest ^:skip-windows wait-for-path-test
   (let [temp-dir-path (System/getProperty "java.io.tmpdir")]
     (is (not= :timed-out
               (bb nil (format "(let [tdir (io/file \"%s\")
@@ -332,7 +339,7 @@
 
 (deftest writer-test
   (let [tmp-file (java.io.File/createTempFile "bbb" "bbb")
-        path (.getPath tmp-file)]
+        path (test-utils/escape-file-paths (.getPath tmp-file))]
     (bb nil (format "(with-open [w (io/writer \"%s\")]
                        (.write w \"foobar\n\")
                        (.append w \"barfoo\n\")
@@ -341,20 +348,22 @@
     (is (= "foobar\nbarfoo\n" (slurp path)))))
 
 (deftest binding-test
-  (is (=  6 (bb nil "(def w (java.io.StringWriter.))
+  (is (= (if main/windows? 7 6)
+        (bb nil "(def w (java.io.StringWriter.))
                  (binding [clojure.core/*out* w]
                    (println \"hello\"))
                  (count (str w))"))))
 
 (deftest with-out-str-test
-  (is (= 6 (bb nil "(count (with-out-str (println \"hello\")))"))))
+  (is (= (if main/windows? 7 6)
+        (bb nil "(count (with-out-str (println \"hello\")))"))))
 
 (deftest with-in-str-test
   (is (= 5 (bb nil "(count (with-in-str \"hello\" (read-line)))"))))
 
 (deftest java-nio-test
   (let [f (java.io.File/createTempFile "foo" "bar")
-        temp-path (.getPath f)
+        temp-path (test-utils/escape-file-paths (.getPath f))
         p (.toPath (io/file f))
         p' (.resolveSibling p "f2")
         f2 (.toFile p')]
@@ -399,11 +408,14 @@
   (is (true? (bb nil "(set! *warn-on-reflection* true)"))))
 
 (deftest clojure-main-repl-test
-  (is (= "\"> foo!\\nnil\\n> \"\n" (test-utils/bb nil "
+  (let [expected-outcome (if main/windows?
+                           "\"> foo!\\r\\nnil\\r\\n> \"\n"
+                           "\"> foo!\\nnil\\n> \"\n")]
+    (is (= expected-outcome (test-utils/bb nil "
 (defn foo [] (println \"foo!\"))
 (with-out-str
   (with-in-str \"(foo)\"
-    (clojure.main/repl :init (fn []) :prompt (fn [] (print \"> \")))))"))))
+    (clojure.main/repl :init (fn []) :prompt (fn [] (print \"> \")))))")))))
 
 (deftest command-line-args-test
   (is (true? (bb nil "(nil? *command-line-args*)")))
@@ -423,7 +435,7 @@
 (deftest uberscript-test
   (let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
     (.deleteOnExit tmp-file)
-    (is (empty? (bb nil "--uberscript" (.getPath tmp-file) "-e" "(System/exit 1)")))
+    (is (empty? (bb nil "--uberscript" (test-utils/escape-file-paths (.getPath tmp-file)) "-e" "(System/exit 1)")))
     (is (= "(System/exit 1)" (slurp tmp-file)))))
 
 (deftest unrestricted-access
@@ -438,16 +450,37 @@
   (testing "writer"
     (is (string? (bb nil "(let [sw (java.io.StringWriter.)] (clojure.pprint/pprint (range 10) sw) (str sw))"))))
   (testing "*print-right-margin*"
-    (is (= "(0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9)\n" (bb nil "
+    (is (= "(0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9)\n"
+           (bb nil "
 (let [sw (java.io.StringWriter.)]
   (binding [clojure.pprint/*print-right-margin* 5]
     (clojure.pprint/pprint (range 10) sw)) (str sw))")))
-    (is (= "(0 1 2 3 4 5 6 7 8 9)\n" (bb nil "
+    (is (= "(0 1 2 3 4 5 6 7 8 9)\n"
+           (bb nil "
 (let [sw (java.io.StringWriter.)]
   (binding [clojure.pprint/*print-right-margin* 50]
     (clojure.pprint/pprint (range 10) sw)) (str sw))"))))
   (testing "print-table writes to sci/out"
-    (is (str/includes? (test-utils/bb "(with-out-str (clojure.pprint/print-table [{:a 1} {:a 2}]))") "----"))))
+    (is (str/includes? (test-utils/bb "(with-out-str (clojure.pprint/print-table [{:a 1} {:a 2}]))") "----")))
+  (testing "cl-format outputs"
+    (testing "cl-format true writes to sci/out"
+      (is (= "[1, 2, 3]" (bb nil "(with-out-str (clojure.pprint/cl-format true \"~<[~;~@{~w~^, ~:_~}~;]~:>\" [1,2,3]))"))))
+    (testing "cl-format nil returns a string"
+      (is (= "forty-two" (bb nil "(clojure.pprint/cl-format nil \"~R\" 42)"))))
+    (testing "cl-format with a writer uses the writer"
+      (is (= "1,234,567      " (bb nil "
+(let [sw (java.io.StringWriter.)]
+   (clojure.pprint/cl-format sw \"~15@<~:d~>\" 1234567)
+   (str sw))")))))
+  (testing "formatter-out"
+    (is (= "[1, 2, 3]\n"
+           (bb nil (pr-str '(do (require '[clojure.pprint :as pprint])
+                                (def print-array (pprint/formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>"))
+                                (pprint/with-pprint-dispatch
+                                  #(if (seqable? %)
+                                     (print-array %)
+                                     (print %))
+                                  (with-out-str (pprint/pprint [1 2 3]))))))))))
 
 (deftest read-string-test
   (testing "namespaced keyword via alias"
@@ -457,13 +490,18 @@
 (deftest available-stream-test
   (is (= 0 (bb nil "(.available System/in)"))))
 
-(deftest file-reader-test
+(deftest ^:skip-windows file-reader-test
   (when (str/includes? (str/lower-case (System/getProperty "os.name")) "linux")
     (let [v (bb nil "(slurp (io/reader (java.io.FileReader. \"/proc/loadavg\")))")]
       (prn "output:" v)
       (is v))))
 
-(deftest download-and-extract-test
+(deftest win-file-reader-test
+  (let [v (bb nil "(slurp (io/reader (java.io.FileReader. \"test-resources/babashka/empty.clj\")))")]
+    (prn "output:" v)
+    (is (empty? v))))
+
+(deftest ^:skip-windows download-and-extract-test
   ;; Disabled because Github throttles bandwidth and this makes for a very slow test.
   ;; TODO: refactor into individual unit tests
   ;; One for downloading a small file and one for unzipping.
@@ -484,7 +522,7 @@
 (deftest delete-on-exit-test
   (when test-utils/native?
     (let [f (java.io.File/createTempFile "foo" "bar")
-          p (.getPath f)]
+          p (test-utils/escape-file-paths (.getPath f))]
       (bb nil (format "(.deleteOnExit (io/file \"%s\"))" p))
       (is (false? (.exists f))))))
 
@@ -639,7 +677,7 @@ true")))
     (is (str/blank? (with-out-str (main/main "doc" "non-existing"))))
     (is (= 1 (main/main "doc" "non-existing")))))
 
-(deftest process-handler-info-test
+(deftest ^:skip-windows process-handler-info-test
   (when test-utils/native?
     (is (= ["-e" "(vec (.get (.arguments (.info (java.lang.ProcessHandle/current)))))"]
            (bb nil "-e" "(vec (.get (.arguments (.info (java.lang.ProcessHandle/current)))))")))
@@ -647,6 +685,12 @@ true")))
          (bb nil "-e" "(.get (.command (.info (java.lang.ProcessHandle/current))))")
          "bb"))))
 
+(deftest ^:windows-only win-process-handler-info-test
+  (when (and test-utils/native? main/windows?)
+    (is (str/ends-with?
+          (bb nil "-e" "(.get (.command (.info (java.lang.ProcessHandle/current))))")
+          "bb.exe"))))
+
 (deftest interop-concurrency-test
   (is (= ["true" 3] (last (bb nil "-e"
                               "
diff --git a/test/babashka/pod_test.clj b/test/babashka/pod_test.clj
index 0b20ff58..f6c8e136 100644
--- a/test/babashka/pod_test.clj
+++ b/test/babashka/pod_test.clj
@@ -1,23 +1,29 @@
 (ns babashka.pod-test
-  (:require [babashka.test-utils :as tu]
+  (:require [babashka.main :as main]
+            [babashka.test-utils :as tu]
             [clojure.edn :as edn]
             [clojure.test :as t :refer [deftest is]]))
 
 (deftest pod-test
   (if (= "true" (System/getenv "BABASHKA_POD_TEST"))
     (let [native? tu/native?
+          windows? main/windows?
           sw (java.io.StringWriter.)
           res (apply tu/bb {:err sw}
                      (cond-> ["-f" "test-resources/pod.clj"]
                        native?
-                       (conj "--native")))
+                       (conj "--native")
+                       windows?
+                       (conj "--windows")))
           err (str sw)]
       (is (= "6\n1\n2\n3\n4\n5\n6\n7\n8\n9\n\"Illegal arguments / {:args (1 2 3)}\"\n(\"hello\" \"print\" \"this\" \"debugging\" \"message\")\ntrue\n" res))
       (when-not tu/native?
-        (is (= "(\"hello\" \"print\" \"this\" \"error\")\n" err)))
+        (is (= "(\"hello\" \"print\" \"this\" \"error\")\n" (tu/normalize err))))
       (is (= {:a 1 :b 2}
              (edn/read-string
               (apply tu/bb nil (cond-> ["-f" "test-resources/pod.clj" "--json"]
                                  native?
-                                 (conj "--native")))))))
+                                 (conj "--native")
+                                 windows?
+                                 (conj "--windows")))))))
     (println "Skipping pod test because BABASHKA_POD_TEST isn't set to true.")))
diff --git a/test/babashka/scripts/System.bb b/test/babashka/scripts/System.bb
index bb9916cb..7fa71e5f 100644
--- a/test/babashka/scripts/System.bb
+++ b/test/babashka/scripts/System.bb
@@ -1,5 +1,5 @@
 [(System/getProperty "user.dir")
  (System/getProperty "foo" "bar")
- (System/getenv "HOME")
+ (or (System/getenv "HOME") (System/getenv "HOMEPATH"))
  (System/getProperties)
  (System/getenv)]
diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj
index 79cd85c2..0c6da084 100644
--- a/test/babashka/test_utils.clj
+++ b/test/babashka/test_utils.clj
@@ -8,16 +8,25 @@
    [clojure.edn :as edn]
    [clojure.string :as str]
    [clojure.test :as test :refer [*report-counters*]]
+   [clojure.tools.reader.reader-types :as r]
    [sci.core :as sci]
    [sci.impl.vars :as vars]))
 
 (set! *warn-on-reflection* true)
 
-
-(defn normalize [s]
+(def normalize
   (if main/windows?
-    (str/replace s "\r\n" "\n")
-    s))
+    (fn [s] (if (string? s)
+              (str/replace s "\r\n" "\n")
+              s))
+    identity))
+
+(def escape-file-paths
+  (if main/windows?
+    (fn [s] (if (string? s)
+              (str/replace s "\\" "\\\\")
+              s))
+    identity))
 
 (def ^:dynamic *bb-edn-path* nil)
 
@@ -26,11 +35,12 @@
   (println))
 
 (defmethod clojure.test/report :end-test-var [_m]
-  (let [{:keys [:fail :error]} @*report-counters*]
-    (when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST"))
-               (or (pos? fail) (pos? error)))
-      (println "=== Failing fast")
-      (System/exit 1))))
+  (when-let [rc *report-counters*]
+    (let [{:keys [:fail :error]} @rc]
+      (when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST"))
+                 (or (pos? fail) (pos? error)))
+        (println "=== Failing fast")
+        (System/exit 1)))))
 
 (defn bb-jvm [input-or-opts & args]
   (reset! cp/cp-state nil)
@@ -47,7 +57,8 @@
         in (if (string? input-or-opts)
              input-or-opts (:in input-or-opts))
         is (when in
-             (java.io.StringReader. in))
+             (r/indexing-push-back-reader
+              (r/push-back-reader (java.io.StringReader. in))))
         bindings-map (cond-> {sci/out os
                               sci/err es}
                        is (assoc sci/in is))]
@@ -86,7 +97,7 @@
         exit (:exit res)
         error? (pos? exit)]
     (if error? (throw (ex-info (or (:err res) "") {}))
-        (:out res))))
+               (normalize (:out res)))))
 
 (def bb
   (case (System/getenv "BABASHKA_TEST_ENV")
diff --git a/test/babashka/transit_test.clj b/test/babashka/transit_test.clj
index 86523771..f8a7eb7a 100644
--- a/test/babashka/transit_test.clj
+++ b/test/babashka/transit_test.clj
@@ -10,4 +10,4 @@
 (deftest transit-test
   (is (= "\"foo\"\n{:a [1 2]}\n"
          (bb (format "(load-file \"%s\")"
-                     (.getPath (io/file "test-resources" "babashka" "transit.clj")))))))
+               (test-utils/escape-file-paths (.getPath (io/file "test-resources" "babashka" "transit.clj"))))))))
diff --git a/test/babashka/uberjar_test.clj b/test/babashka/uberjar_test.clj
index ee2e85a3..581a863d 100644
--- a/test/babashka/uberjar_test.clj
+++ b/test/babashka/uberjar_test.clj
@@ -1,8 +1,9 @@
 (ns babashka.uberjar-test
   (:require
-   [babashka.test-utils :as tu]
-   [clojure.string :as str]
-   [clojure.test :as t :refer [deftest is testing]]))
+    [babashka.main :as main]
+    [babashka.test-utils :as tu]
+    [clojure.string :as str]
+    [clojure.test :as t :refer [deftest is testing]]))
 
 (defn count-entries [jar]
   (with-open [jar-file (java.util.jar.JarFile. jar)]
@@ -44,17 +45,21 @@
         (is (= "(\"42\")\n" (tu/bb nil "--jar" path "-m" "my.main-main" "42")))
         (is (= "(\"42\")\n" (tu/bb nil "--classpath" path "-m" "my.main-main" "42")))
         (is (= "(\"42\")\n" (tu/bb nil path "42"))))))
-  (testing "throw on empty classpath"
-    (let [tmp-file (java.io.File/createTempFile "uber" ".jar")
-          path (.getPath tmp-file)]
-      (.deleteOnExit tmp-file)
-      (is (thrown-with-msg?
-           Exception #"classpath"
-           (tu/bb nil "uberjar" path "-m" "my.main-main")))))
+
+  ; this test fails the windows native test in CI
+  (when-not main/windows?
+    (testing "throw on empty classpath"
+      (let [tmp-file (java.io.File/createTempFile "uber" ".jar")
+            path     (.getPath tmp-file)]
+        (.deleteOnExit tmp-file)
+        (is (thrown-with-msg?
+              Exception #"classpath"
+              (tu/bb nil "uberjar" path "-m" "my.main-main"))))))
   (testing "ignore empty entries on classpath"
     (let [tmp-file (java.io.File/createTempFile "uber" ".jar")
-          path (.getPath tmp-file)]
+          path (.getPath tmp-file)
+          empty-classpath (if main/windows? ";;;" ":::")]
       (.deleteOnExit tmp-file)
-      (tu/bb nil "--classpath" ":::" "uberjar" path "-m" "my.main-main")
+      (tu/bb nil "--classpath" empty-classpath "uberjar" path "-m" "my.main-main")
       ;; Only a manifest entry is added
       (is (< (count-entries path) 3)))))
diff --git a/test/babashka/udp_test.clj b/test/babashka/udp_test.clj
index 5c0f3730..dbe65ffe 100644
--- a/test/babashka/udp_test.clj
+++ b/test/babashka/udp_test.clj
@@ -22,4 +22,4 @@
              "-e" "(load-file (io/file \"test-resources\" \"babashka\" \"statsd.clj\"))"
              "-e" "(require '[statsd-client :as c])"
              "-e" "(c/increment :foo)"))
-    (is (= ":foo:1|c\n" (str sw)))))
+    (is (= ":foo:1|c\n" (tu/normalize (str sw))))))