-
-
Notifications
You must be signed in to change notification settings - Fork 7
Implement Clojurescript port for self-hosted Clojurescript. #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Requires planck.
Wow, this will take me a while to digest and respond to but at first glance, this looks workable "as-is" so I'll spend some time reviewing it at work tomorrow and will probably merge it and then we can hash some of the rest out in a discussion here and decide what, if any, changes need to be made before cutting a release. Big, big thank you for all the work you've put in on this! |
2/3/4. re: top-level requires/HTO -- I'm surprised cljs doesn't allow that (I guess I'm going to be surprised by most differences!) but as long as HTO remains an optional dependency in the clj version, I'm happy -- and you'll be the only cljs user of the library for now so as long as you're happy always using HTO, I'm also happy. HTO makes some failure reports a lot worse so I don't actually use it (but I know some folks love it -- and one of the biggest complaints about
TBC... |
15a) re: 15b) re: string diffs -- that seems like a reasonable enhancement to have built-in so I'd be interested in a PR for that. |
Re: 13. My version of macOS is too old to install |
Re: 1. I let Parinfer "fix" the formatting -- 6c6adbe -- Just FYI. I don't know whether this makes it better or worse from your p.o.v. :) |
Re: 13, part 2. On my Windows laptop tonight and installed Also, I pointed our large test suite at work to the develop branch locally and everything still works, so that feels like a good sanity check that nothing broke in the clj side of the house. Big thanks again for all this work! |
Thanks for all of the responses, particularly the most recent one where you can now run the cljs tests! I went to sleep last night trying to figure out how to make this work in non-self-hosted cljs, and near as I can tell, the places in the Other responses: One thing I've noticed is that while I want 2/3/4/HTO. I'm not an HTO user, I'm a convert from classic expectations. I figured "more is better", so I figured that we should include HTO. I would be a fan of dumping HTO over time and moving more of the classic expectations-style failure comparisons into
Source formatting -- I'll see if I can find a way to get you an out-of-band copy of Thanks for accepting this PR! |
2/3/4/HTO: There's a string-differ in Classic Expectations that could be used as-is (it's referenced in the docstring for
Re: source formatting: also sounds good. I'll know as soon as I open the file whether Parinfer will want to mess with it or not :) Make sure future PRs are based against the latest develop since I've put in a bunch of changes since merging this (including letting Parinfer "fix" the indentation in every file!). |
Here it is. I hope I have all of the pieces together -- there are a lot of moving parts in this pull request! I've done the code and made a pass at documenting it as well, so in addition to this rather long list of things here, the documentation (new file
doc/getting-started-cljs.md
and the end of theREADME.md
) might be useful for you. I've tried really hard to make sure it is all together, but it wouldn't surprise me if I've missed something somewhere.Notes I made while working on the code:
Very impressive use of macros! I learned a lot just by working on this. Both the basic expectations ones and the testing ones. Amazing!
I have made all of the source changes by trying to change as few lines as possible, and have hand formatted the things I did change. The point is to have a diff show the bare minimum set of changes, so you can easily see what I have done. Given that I am a big proponent of source formatting, this has been a challenge, to be sure. I also didn't want to intrude on your approach to source formatting (though I expect that I could configure zprint to do it "your way"). But I tried to
minimize changes. The formatting of what you see is not ideal from my standpoint, but the point is to minimize your effort for review.
One can't really do run-time requires in cljs:
So if we are to allow humane-test-output, it has to be there all the time. I think we should, since every bit of help is worth it.
The alternative would be to do more of the diffing like classic expectations does and output it ourselves. Which, if you think it would be useful, is something I would consider contributing (as I already have code to handle strings, discussed much later in this list). If we did that, we could dump HTO in cljs and maybe in clj as well. But for now, the cljs version requires HTO and hacks it to work with the locally created
:diffs
. See below for details.The same goes for spec -- no way to really use it only if it is available. So, the cljs version requires spec to be there. Which isn't a big deal.
(pjstadig.util/define-fail-report)
to get it to work in cljs.Here is the code for that, which you may care about for the next item:
The cljs/cljc version of humane-test-output handles :diffs differently than the clj version (i.e., it ignores whatever you pass in), so I had to kind of hack a patch into it as part of activating it. See the cljs code at "(def humane-test-output?" in test.cljc. It would be reasonable to get them to change that I suppose. This change seemed really awful when I first hacked it in, but over time I became more tolerant of it. I'm not sure why. I suppose because I didn't want to wait for two libraries to change just to get it to work at all. And because it is so simple as to be transparent, I suppose.
I had to use a number of
planck
routines to get this to work, so it is clearly wedded to planck. I suspect that lumo, the other self-hosted cljs environment, has similar routines and that it could be made to work there as well. I think the macro situation is so different in regular cljs that I don't have any idea how to make this work, since without a cljs environment around when you expand the macros, none of the resolve (or find-var) stuff is going to work.Exceptions exist and can be thrown in Javascript. You can throw pretty much anything in Javascript. There is no
Throwable
class in cljs to distinguish things that can be thrown from anything else. This makes it hard to distinguish a potential Exception from anything else that shows up as "expected" and know when to generate the(is thrown? ...)
form.At present the only exception you can look for (i.e., where
(is thrown? ...)
is used) is js/Error. That isn't substantially different from what classic expectations does with cljs exceptions, I think.I don't know if this is a major limitation or not. I suspect that I could figure out how to support
:default
as theexpected
, and in that case I would accept anything that was thrown, but not by using(is thrown? ...)
, rather by doing a(try (catch :default ...))
which is a special cljs thing to catch anything that was thrown. I can work on this if you want (or we could do that in a later update).I spent way too long dealing with the fact that classes (such as they are in Javascript) and functions both return true to
(fn? ...)
. Plus, there is no(class? ...)
in cljs. This makes distinguishing predicates from classes really hard. I have managed to separate them by looking at the results of printing them, as functions print as"#object[Function]"
, where classes do not. I could, instead, have looked at the length of the arglist, as classes have a null arglist and predicates to not. But however it is done, this distinction is vital. Awful, I think, but vital.use-fixtures -- this is a macro in cljs.test, and apparently we just really want the cljs.test behavior. So we pass through directly to the cljs.test macro using our own macro. The approach for clj using apply doesn't work in cljs.
from-clojure-test: This is a challenge in cljs, where you can't modify metadata on vars. Planck has a way to do this, and it seems to work for clj as well, so I went with that. The arglists just don't come over, and I don't know why or if that will become a problem. Doesn't seem to be problem so far.
Using the cljs version of humane-test-output with a modern cljs version produces lots of warnings. HTO doesn't really do anything wrong, it uses what appears to be a legitimate call to a function in
cljs.pprint
, but that call uses private functions which (relatively) recently have started to throw warnings during cljs compilation. These warnings can be disabled, which I have done by passing some options to planck. Another reason to dump HTO, I suppose, long term.Getting the clj tests to work for cljs was at least as much work as getting
test.cljc
modified, perhaps more. The only remaining pain is that the spec testing doesn't work if the(s/def ...)
is in the same compilation unit as the(deftest ... (expect ...))
form. To work around this, I stuck it at the end oftest.cljc
for cljs instead of making yet another file in the testing area. (It also doesn't work if it is intest_macros.cljc
). Everybody who uses expectations.clojure.test for cljs will therefore get a single(s/def ...)
spec defined, but since it is namespaced it doesn't seem like a terrible thing. I expect that another file in the testing area would also solve this if you prefer. Seems odd to have a file with one line in it, but that would almost certainly work.I changed the artifact-id to be
cljc-test
fromcloure-test"
and learned that the artifact-id isn't really related to the namespace, which was news to me. Usually they are related so I assumed that was necessary, which it appears it is not. In our discussion on github, I thought that you implied that changing the artifact-id and not the namespace would be your favored solution to the restrictions indoo
for self-hosted cljs. I probably would have changed the namespace to follow the artifact-id so that they went together, for no reason other than trying to reduce surprises for users (and because I incorrectly thought it was required). But this works, and seems reasonable now that I've lived with it a while. I would be a a lot of work to change the namespace everywhere, and disrupt current users as well, so this seems like a good compromise (though the artifact-id change will disrupt current users a bit as well when they upgrade).The end of README.md has instructions for running the cljs tests, as well as how to get a planck cljs REPL working with
expectations/clojure-test
loaded inside if you want to experiment with it.Getting cljdoc to work was, as usual, interesting. I love cljdoc, but I find it challenging to get it to analyze cljs files without a lot of work. This time was no exception. The
planck.core
namespace is built into planck, but ... the cljs analysis phase complains that it needsplanck.core
. Sigh. So, I eventually found aplanck.core
that actually seems to work in cljdoc on its own, and supplied that and now cljdoc works. But we don't really want planck as a dependency forexpectations/clojure-test
, since it is only necessary to get cljdoc to work. And there is no way to tell cljdoc to use this special thing and not have it be part of the dependencies for the .jar for everyone (near as I can tell, anyway). After a lot of trial and error, emphasis on the error, it seems like the only approach I can find usingdeps.edn
is to edit planck intopom.xml
with "scope provided". Having done that, it turns out that HTO needs the same treatment, so they are both edited intopom.xml
by hand, with "scope provided". Thus, HTO isn't brought in for clj operations unless you want it, and it must be supplied by the user for cljs operations since I could find no way to have a.jar
file with different deps for clj and cljs let alone a way to specify this indeps.edn
. This means that cljs users have to specify HTO on their own, it doesn't get included as a dependency from the .jar.This all seems to work, but leaves the
pom.xml
file something that needs to be edited by hand, but that seems like the way of things in thedeps.edn
world in any case, so I suppose it is ok. It is already the case that the artifact-id doesn't show up anywhere exceptpom.xml
, which is slightly worrisome. I'm just not used to hand-editing thepom.xml
, I suppose. I think of it as something derived from more user friendly configuration information.The bottom line is, I have run cljdoc locally and it works with the current documentation. But don't remove planck or HTO from the pom.xml or it will stop working. I put
[planck "2.23.0"]
indeps.edn
under the:cljdoc
alias, even though there is actually no need for it to be there, but it worried me that having it only in the pom.xml would mean it would just get lost.expectations/clojure-test
for both clj and cljs which I find helpful (and almost necessary for my use case). I have not included them in this pull request, but will add them for your review when (if?) we get the current Clojurescript changes integrated::a) I have added the "actual" form (before evaluation) to the msg that comes out when a failure occurs. This is useful for two reasons:
Clojurescript doesn't yet do file names and line numbers pretty much at all (though that is apparently coming), so that when a test fails, it can be mighty hard to figure out which test is failing. With the "actual" form, a simple text search is easy.
I can cut the "actual" form from the failure report and paste it into a repl and reproduce the failure without even having to find it in the source for the tests. This was also possible with the classic expectations, and I missed that capability a lot.
Given that I have 945 tests that run in cljs and 1002 in clj, this is a big win in my environment.
b) I have a lot of tests that expect a string as output, and it is often very long string which wraps onto many multiple lines. The HTO handling of string diffs is pretty much =/not=, which is a long way from the classic Expectations "matched/diverges" approach to strings, and pretty useless for strings that wrap.
So I have implemented the equivalent support to the classic Expectations string handling and stuck that in the msg when the expected and actual are both strings and they don't compare. This helps me out a lot, and almost certainly wouldn't hurt anyone else. The code is short and simple and common to both clj and cljs.
That's it for now.