4 votes

Comment tester les deux côtés d'un `if @generated` ?

Considérons que je pourrais avoir quelques fonctions générées que je veux tester comme ci-dessous. Suivant recommandation du manuel Je les ai fait facultativement généré .

# This code is taken directly from Base. 
# https://github.com/JuliaLang/julia/blob/592748adb25301a45bd6edef3ac0a93eed069852/base/namedtuple.jl#L220-L231
# So importing some of the helpers
using Base: merge_names, merge_types, sym_in

function my_merge_fail1(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn}
    if @generated
        names = merge_names(an, bn)
        types = merge_types(names, a, b)
        vals = Any[ :(getfield($(sym_in(n, bn) ? :b : :a), $(QuoteNode(n)))) for n in names ]
        :(error("typo1"); NamedTuple{$names,$types}(($(vals...),)) )
    else
        names = merge_names(an, bn)
        types = merge_types(names, typeof(a), typeof(b))
        NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names))
    end
end

function my_merge_fail2(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn}
    if @generated
        names = merge_names(an, bn)
        types = merge_types(names, a, b)
        vals = Any[ :(getfield($(sym_in(n, bn) ? :b : :a), $(QuoteNode(n)))) for n in names ]
        :(NamedTuple{$names,$types}(($(vals...),)) )
    else
        error("typo2")
        names = merge_names(an, bn)
        types = merge_types(names, typeof(a), typeof(b))
        NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names))
    end
end

Maintenant, il s'avère que j'ai fait une "coquille", dans chaque. J'ai inclus par erreur error(...) dans les deux. Sur my_merge_fail1 J'ai fait l'erreur dans le @generated branche, et dans my_merge_fail2 dans la branche non générée.

Mes tests ne semblent le détecter que dans une seule branche :

julia> using Test

julia> @test my_merge_fail1((a=1,), (b=2,)) == (a=1, b=2)
Error During Test at REPL[12]:1
  Test threw exception
  Expression: my_merge_fail1((a = 1,), (b = 2,)) == (a = 1, b = 2)
  typo1
  Stacktrace:
   [1] error(::String) at ./error.jl:33
   [2] macro expansion at ./REPL[6]:2 [inlined]
   [3] my_merge_fail1(::NamedTuple{(:a,),Tuple{Int64}}, ::NamedTuple{(:b,),Tuple{Int64}}) at ./REPL[6]:2
   [4] top-level scope at REPL[12]:1
   [5] eval(::Module, ::Any) at ./boot.jl:331
   [6] eval_user_input(::Any, ::REPL.REPLBackend) at /usr/local/src/julia/julia-master/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:86
   [7] macro expansion at /usr/local/src/julia/julia-master/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:118 [inlined]
   [8] (::REPL.var"#26#27"{REPL.REPLBackend})() at ./task.jl:333

ERROR: There was an error during testing

julia> @test my_merge_fail2((a=1,), (b=2,)) == (a=1, b=2)
Test Passed

Comment puis-je améliorer mes tests ?

1voto

Mason Points 1430

Une solution serait de faire des deux branches leurs propres fonctions indépendantes que vous pouvez tester directement.

using Base: merge_names, merge_types, sym_in

function my_merge_fail(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn}
    if @generated
        :(my_merge_fail_inner_gen(a, b))
    else
        my_merge_fail_inner(a, b)
    end
end

@generated function my_merge_fail_inner_gen(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn}
    names = merge_names(an, bn)
    types = merge_types(names, a, b)
    vals = Any[ :(getfield($(sym_in(n, bn) ? :b : :a), $(QuoteNode(n)))) for n in names ]
    :(NamedTuple{$names,$types}(($(vals...),)) )
end

function my_merge_fail_inner(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn}
    error("typo2")
    names = merge_names(an, bn)
    types = merge_types(names, typeof(a), typeof(b))
    NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names))
end

puis on exécute les tests comme ça :

julia> using Test

julia> @testset "my_merge_fail" begin
           @test my_merge_fail(          (a=1,), (b=2,)) == (a=1, b=2)
           @test my_merge_fail_inner_gen((a=1,), (b=2,)) == (a=1, b=2)
           @test my_merge_fail_inner(    (a=1,), (b=2,)) == (a=1, b=2)
       end
my_merge_fail: Error During Test at REPL[18]:4
  Test threw exception
  Expression: my_merge_fail_inner((a = 1,), (b = 2,)) == (a = 1, b = 2)
  typo2
  Stacktrace:
   [1] error(::String) at ./error.jl:33
   [2] my_merge_fail_inner(::NamedTuple{(:a,),Tuple{Int64}}, ::NamedTuple{(:b,),Tuple{Int64}}) at ./REPL[8]:2
   [3] top-level scope at REPL[18]:4
   [4] top-level scope at /Users/mason/julia/usr/share/julia/stdlib/v1.3/Test/src/Test.jl:1107
   [5] top-level scope at REPL[18]:2

Test Summary: | Pass  Error  Total
my_merge_fail |    2      1      3
ERROR: Some tests did not pass: 2 passed, 0 failed, 1 errored, 0 broken.

Je ne suis pas certain que cette refactorisation ait un quelconque effet sur l'heuristique utilisée par Julia pour décider si elle doit utiliser l'option de l'utilisateur. @generated ou non. Si quelqu'un connaît la réponse à cette question, un commentaire ou une suggestion de modification serait apprécié.

1voto

Simeon Schaub Points 450

Il est possible d'y parvenir en extrayant le code abaissé de la méthode, où vous pouvez spécifier si vous obtenez celui de la version générée ou non de cette méthode. Cette approche utilise eval y Base.invokelatest Il y aura donc une certaine surcharge, mais cela devrait être suffisant pour les tests.

function _invoke(f, x...; generated)
    sig = Core.Typeof(x)
    ci = code_lowered(f, sig; generated)[1]
    Meta.partially_inline!(
        ci.code, [], Tuple{typeof(f),sig.parameters...}, Any[sig.parameters...],
        0, 0, :propagate,
    )
    g = @eval @generated function $(gensym(:g))($([gensym() for _ in x]...))
        return $(QuoteNode(ci))
    end
    Base.invokelatest(g, x...)
end

Vous pouvez alors choisir d'invoquer soit la version générée, soit la version non générée de la méthode, comme ceci :

julia> f(x, y) = @generated() ? :x : y
f (generic function with 1 method)

julia> _invoke(f, 1, 2; generated=true)
1

julia> _invoke(f, 1, 2; generated=false)
2

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X