-
Couldn't load subscription status.
- Fork 9
[WIP] Try to filter test items without constructing the AST #226
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
base: td-include-tasks
Are you sure you want to change the base?
Changes from 5 commits
97a43ca
60fea81
d124aa8
33b7073
456f1d1
c33eccd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,21 +4,25 @@ version = "1.34.0" | |
|
|
||
| [deps] | ||
| Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" | ||
| JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4" | ||
| Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" | ||
| Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" | ||
| Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" | ||
| Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" | ||
| StringViews = "354b36f9-a18e-4713-926e-db85100087ba" | ||
| Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
| TestEnv = "1e6cf692-eddd-4d53-88a5-2d735e33781b" | ||
|
|
||
| [compat] | ||
| Dates = "1" | ||
| JuliaSyntax = "0.4, 1" | ||
| Logging = "1" | ||
| Pkg = "1" | ||
| Profile = "1" | ||
| Random = "1" | ||
| Serialization = "1" | ||
| Sockets = "1" | ||
| StringViews = "1" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can't (don't want) any non-stdlib deps... how much benefit does StringViews give us? Our options are (i think): drop it, reimplement a minimal version, vendor it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only thing this gives is the ability to run regexes against a view of a Vector{UInt8} (and comparing them to Strings, but that is easy to do manually) |
||
| Test = "1" | ||
| TestEnv = "1.8" | ||
| julia = "1.8" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| using JuliaSyntax: JuliaSyntax, ParseError, ParseStream, @K_str | ||
| using JuliaSyntax: any_error, build_tree, first_byte, kind, parse!, peek_full_token, peek_token | ||
| using StringViews | ||
|
|
||
| function include_test_file(ti_filter::TestItemFilter, path::String) | ||
| bytes = read(path) | ||
| stream = ParseStream(bytes) | ||
| line_starts = Int32[Int32(i) for (i, x) in enumerate(bytes) if x == 0x0a] # :( | ||
| tls = task_local_storage() | ||
| tls[:SOURCE_PATH] = path # This is also done by Base.include | ||
| try | ||
| @inbounds while true | ||
| JuliaSyntax.bump_trivia(stream, skip_newlines=true) | ||
| t = peek_token(stream, 1) | ||
| k = kind(t) | ||
| k == K"EndMarker" && break | ||
| line = _source_line_index(line_starts, first_byte(stream)) | ||
| if k == K"@" | ||
| tf = peek_full_token(stream, 2) | ||
| v = @view(bytes[tf.first_byte:tf.last_byte]) | ||
| if v == b"testitem" ; _eval_test_item_stream(stream, path, line, ti_filter, bytes) | ||
| elseif v == b"testsetup"; _eval_test_setup_stream(stream, path, line) | ||
| elseif v == b"test_rel" ; _eval_test_rel_stream(stream, path, line, ti_filter, bytes) | ||
| else | ||
| error("Test files must only include `@testitem` and `@testsetup` calls, got an `@$(StringView(v))` at $(path):$(line)") # TODO | ||
| end | ||
| else | ||
| error("Test files must only include `@testitem` and `@testsetup` calls, got a $t at $(path):$(line)") # TODO | ||
| end | ||
| empty!(stream) # TODO: SourceFile created on a reset stream always start line number at 1 | ||
| end | ||
| finally | ||
| delete!(tls, :SOURCE_PATH) | ||
| end | ||
| end | ||
|
|
||
| @inline function _source_line_index(line_starts, bytes_pos) | ||
| lineidx = searchsortedfirst(line_starts, bytes_pos) | ||
| return (lineidx < lastindex(line_starts)) ? lineidx : lineidx-1 | ||
| end | ||
|
|
||
| # unconditionally eval | ||
| function _eval_test_setup_stream(stream, path, line) | ||
| parse!(stream; rule=:statement) | ||
| ast = build_tree(Expr, stream; filename=path, first_line=line) | ||
| Core.eval(Main, ast) | ||
| return nothing | ||
| end | ||
|
|
||
| # test_rel -> apply ti_filter on the parsed ast | ||
| function _eval_test_rel_stream(stream, path, line, ti_filter::TestItemFilter, bytes) | ||
| parse!(stream; rule=:statement) | ||
| if !(ti_filter.name isa Nothing) | ||
| @inbounds for (i, token) in enumerate(stream.tokens) | ||
| if kind(token) == K"Identifier" | ||
| fbyte = JuliaSyntax.token_first_byte(stream, i) | ||
| lbyte = JuliaSyntax.token_last_byte(stream, i) | ||
| if @view(bytes[fbyte:lbyte]) == b"name" | ||
| fbyte = JuliaSyntax.token_first_byte(stream, i + 3) | ||
| lbyte = JuliaSyntax.token_last_byte(stream, i + 3) | ||
| name = StringView(@view(bytes[fbyte:lbyte])) | ||
| _contains(name, ti_filter.name) && break | ||
| return nothing | ||
| end | ||
| end | ||
| end | ||
| end | ||
| ast = build_tree(Expr, stream; filename=path, first_line=line) | ||
| any_error(stream) && throw(ParseError(stream, filename=path)) | ||
| filtered = __filter_rai(ti_filter, ast)::Union{Nothing, Expr} | ||
| filtered === nothing || Core.eval(Main, filtered::Expr) | ||
| return nothing | ||
| end | ||
|
|
||
| # like above, but tries to avoid parsing the ast if it sees from the name identifier token | ||
| # it won't pass the filter | ||
| function _eval_test_item_stream(stream, path, line, ti_filter::TestItemFilter, bytes) | ||
| if !(ti_filter.name isa Nothing) | ||
| name_t = peek_full_token(stream, 4) # 3 was '\"' | ||
| name = @inbounds StringView(@view(bytes[name_t.first_byte:name_t.last_byte])) | ||
| parse!(stream; rule=:statement) | ||
| _contains(name, ti_filter.name) || return nothing | ||
| else | ||
| parse!(stream; rule=:statement) | ||
| end | ||
|
|
||
| ast = build_tree(Expr, stream; filename=path, first_line=line) | ||
| any_error(stream) && throw(ParseError(stream, filename=path)) | ||
| filtered = __filter_ti(ti_filter, ast)::Union{Nothing, Expr} | ||
| filtered === nothing || Core.eval(Main, filtered::Expr) | ||
| return nothing | ||
| end | ||
|
|
||
| @inline _contains(s::AbstractString, pattern::Regex) = occursin(pattern, s) | ||
| @inline _contains(s::AbstractString, pattern::AbstractString) = s == pattern |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this we'd probably have to vendor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we go from
using JuliaSyntaxtousing Base.JuliaSyntax😈