Skip to content
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

Julia #126

Merged
merged 8 commits into from
Jun 10, 2024
Merged

Julia #126

merged 8 commits into from
Jun 10, 2024

Conversation

rystidia
Copy link

Add tests for compiled tests, running scripts, and KST translator support for Julia

This PR addresses the issue kaitai-io/kaitai_struct#1109

@GreyCat
Copy link
Member

GreyCat commented Apr 29, 2024

Thanks for your contribution, @rystidia! Can I also ask to take one step further and Julia testing runs in Docker? This way:

  • we can do stuff like ./docker-ci -t julia -i some_version to run these tests locally without installing Julia manually,
  • and, probably more importantly, it streamlines CI integration a lot, as we can just reuse exactly the image and results you've created like that.

It should be relatively straightforward to create a Julia image in kaitai_struct_docker_images, following the pattern of existing images.

@rystidia
Copy link
Author

I created this PR (which was merged). It worked for me when running like ./docker-ci -t julia -i 1.9. Should I somehow change it?

@GreyCat
Copy link
Member

GreyCat commented Apr 29, 2024

Nope, sorry, please disregard my previous comment — I've just overlooked that you've already created it. Now it's merged and this enables to test tests locally.

@GreyCat GreyCat merged commit 6879a2f into kaitai-io:master Jun 10, 2024
@@ -0,0 +1,10 @@
#!/bin/bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only run-* script with the #!/bin/bash shebang - all the other run-* scripts use mostly #!/bin/sh -ef. I don't see any reason to use #!/bin/bash here, given that the script apparently doesn't shouldn't have to use any bashisms. Using #!/bin/sh -ef instead will make it more portable, for example it will then work on Alpine Linux that doesn't have bash by default.

Edit: oh, source is actually a bashism, as ShellCheck points out:

Line 4:
source ./config
^-- SC3046 (warning): In POSIX sh, 'source' in place of '.' is undefined.

Well, that's another good reason to use . instead of source, as I suggest below.

Comment on lines +2 to +3


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double newline here:

Suggested change

#!/bin/bash


source ./config
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use

Suggested change
source ./config
. ./config

everywhere.

Comment on lines +6 to +10
# Define the command to run the Julia script with redirected output
command="JULIA_LOAD_PATH=\"$JULIA_LOAD_PATH:$JULIA_RUNTIME_DIR:compiled/julia:spec/julia/extra\" julia \"spec/julia/runtests.jl\""

# Run the command
eval $command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any reason to use eval here. It makes everything ugly by having to escape the double quotes as \", and given that the $command in eval $command is unquoted, it is subject to all the globbing and word splitting as described in SC2086, which in this case means that JULIA_LOAD_PATH and JULIA_RUNTIME_DIR must not contain any spaces or glob characters, otherwise the script might break.

Why not just:

Suggested change
# Define the command to run the Julia script with redirected output
command="JULIA_LOAD_PATH=\"$JULIA_LOAD_PATH:$JULIA_RUNTIME_DIR:compiled/julia:spec/julia/extra\" julia \"spec/julia/runtests.jl\""
# Run the command
eval $command
JULIA_LOAD_PATH="$JULIA_LOAD_PATH:$JULIA_RUNTIME_DIR:compiled/julia:spec/julia/extra" julia spec/julia/runtests.jl

Also, I don't see the value of trivial comments such as Define the command to run the Julia script with redirected output (and I don't think this one actually makes sense - what exactly does "with redirected output" refer to? - I don't see any |, > or >> after the command, so it doesn't look like the output is redirected anywhere).

@@ -71,6 +74,15 @@ def each_test

tr = TestResult.new(name, :skipped, 0, nil)
yield tr
elsif ts.attribute('errors') && ts.attribute('errors').value.to_f != 0 && ts.attribute('name').value =~ /^\/(.*?) test$/
Copy link
Member

@generalmimon generalmimon Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing the errors attribute as a float (.to_f) doesn't make sense. It should be parsed as an integer, preferably using the strictest way possible, which would be Integer(..., 10) (see https://stackoverflow.com/a/49282/12940655).

Alternatively, we might not need to parse it at all, but instead can compare it directly to the string representation like ts.attribute('errors').value != '0' (this is even stricter, but all we really need, since there shouldn't be any other representations of zero anyway).

function run_tests()
#nested testset is needed to continue even after some test errors
@testset "" begin
for f in filter(s -> contains(s, r".jl$") && !contains(s, "runtests.jl"), readdir(@__DIR__; join=true))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This:

    contains(s, r".jl$")

    ... is an unnecessary use of a regex, which actually comes with a subtle bug - the dot . doesn't mean "literal dot" in this context, but "arbitrary character":

    julia> print(contains("hjl", r".jl$"))
    true

    This demonstrates that it is generally best to use the "least powerful" tool for the job - in this case endswith:

    julia> print(endswith("hjl", ".jl"))
    false
    julia> print(endswith("h.jl", ".jl"))
    true

    Simpler tools tend to have less surprises.

  2. To me, this is sloppy:

    !contains(s, "runtests.jl")

    This tests whether the string "runtests.jl" is contained anywhere in the (absolute) file path, which is not really what we want (there's no reason to match helloruntests.jl or directories helloruntests.jl/..., etc.). What we actually want is to take the basename of that path and test it for equality with the string "runtests.jl".

override def header(): Unit = {
importList.add(s"import ${className}Module")
out.puts
out.puts(s"@testset ${'"'}$className test${'"'} begin")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, this way of inserting the literal double quote is unnecessarily complicated - this is how we usually do it:

Suggested change
out.puts(s"@testset ${'"'}$className test${'"'} begin")
out.puts(s"""@testset "$className test" begin""")

See for example:

out.puts(s"""local r = $className:from_file("src/${spec.data}")""")

Comment on lines +67 to +68
override def noAsserts() =
out.puts("")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to override noAsserts just to insert a blank line? All other languages except Python don't override it and just use the default (empty) implementation:

Python is the only language which overrides it, but that actually serves a purpose (Python syntactically requires at least one statement in each block, and pass is idiomatically used if there's nothing to do):

Comment on lines +42 to +50
override def simpleEquality(check: TestEquals): Unit = {
val actStr = translateAct(check.actual)
val expStr = translateExp(check.expected)
out.puts(s"@test $actStr == $expStr")
}

override def floatEquality(check: TestEquals): Unit = {
val actStr = translateAct(check.actual)
val expStr = translator.translate(check.expected)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inconsistency doesn't look intentional - either both should use translateExp, or both should use translator.translate.

Comment on lines +73 to +74
def translateExp(x: Ast.expr) =
translator.translate(x).replace("this._root", className)
Copy link
Member

@generalmimon generalmimon Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like seeing this pattern at all since:

  1. it uses a textual replacement... (which is inherently wrong and should be avoided as much as possible - textual replacement can be only ever appropriate in free-form text, not in anything that has certain syntax and semantics that must be respected, like the code expression of some programming language. The existing .replace("this." + Main.INIT_OBJ_NAME, "r") is also fundamentally flawed, but at least it is very unlikely to cause any actual problems, because val INIT_OBJ_NAME = "q1w2e3" is sufficiently "random" that it probably won't clash with anything unintended.)
  2. with something that doesn't even make sense at first sight (in general you can hardly "replace" _root, which is the reference to the root instance of the top-level class, with the class name itself. That is like apples and oranges).

This shouldn't be necessary, and I'm afraid it indicates some issues with the compiler code generation...

@rystidia rystidia mentioned this pull request Jun 22, 2024
@rystidia
Copy link
Author

@generalmimon Thanks for your detailed review. As a reaction to your suggested changes, I opened !127

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants