diff --git a/Project.toml b/Project.toml index 98b452e..06048c9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SciMLLogging" uuid = "a6db7da4-7206-11f0-1eab-35f2a5dbe1d1" authors = ["Jadon Clugston"] -version = "1.6.0" +version = "1.7.0" [deps] Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" diff --git a/src/SciMLLogging.jl b/src/SciMLLogging.jl index b3bd311..670a341 100644 --- a/src/SciMLLogging.jl +++ b/src/SciMLLogging.jl @@ -6,6 +6,7 @@ using Preferences include("verbosity.jl") include("utils.jl") +include("verbspec_generation_macro.jl") # Export public API export AbstractVerbositySpecifier, AbstractVerbosityPreset, AbstractMessageLevel @@ -15,5 +16,6 @@ export verbosity_to_int, verbosity_to_bool export SciMLLogger export set_logging_backend, get_logging_backend export None, Minimal, Standard, Detailed, All +export @define_verbosity_specifier end diff --git a/src/verbspec_generation_macro.jl b/src/verbspec_generation_macro.jl new file mode 100644 index 0000000..5030b00 --- /dev/null +++ b/src/verbspec_generation_macro.jl @@ -0,0 +1,252 @@ +""" + @verbosity_specifier name begin + toggles = (...) + presets = (...) + groups = (...) + end + +Generates a parametric struct and constructors for a verbosity specifier. + +# Input Format + +**toggles:** Tuple of symbols defining the verbosity toggle names (e.g., `(:toggle1, :toggle2)`). + +**presets:** Named tuple mapping preset names to toggle configurations. Each preset maps toggle names to message levels or preset types. +Must include at least `Standard`. Can define custom presets beyond the standard five (None, Minimal, Standard, Detailed, All). + +**groups:** Named tuple mapping group names to tuples of toggle symbols. Groups allow setting multiple toggles at once. + +# Generated Code + +**Struct:** Creates `name{T1, T2, ...} <: AbstractVerbositySpecifier` with fields for each toggle. + +**Constructors:** +- `name()`: Default constructor using Standard preset +- `name(preset::AbstractVerbosityPreset)`: Constructor from preset (e.g., `name(Minimal())`) for each preset in presets +- `name(; preset=nothing, groups..., kwargs...)`: Keyword constructor with precedence: individual > group > preset + +**Custom Preset Types:** Generates struct definitions for non-standard presets (beyond None, Minimal, Standard, Detailed, All). + +# Example +```julia +@verbosity_specifier MyVerbosity begin + toggles = (:toggle1, :toggle2) + + presets = ( + Standard = ( + toggle1 = InfoLevel(), + toggle2 = WarnLevel() + ), + ) + + groups = ( + group1 = (:toggle1,), + ) +end +``` +""" +macro verbosity_specifier(name, block) + # Extract the three assignments from the block + local toggles_expr = nothing + local presets_expr = nothing + local groups_expr = nothing + + if block.head == :block + for ex in block.args + if ex isa Expr && ex.head == :(=) + lhs = ex.args[1] + rhs = ex.args[2] + if lhs == :toggles + toggles_expr = rhs + elseif lhs == :presets + presets_expr = rhs + elseif lhs == :groups + groups_expr = rhs + end + end + end + end + + toggles_expr !== nothing || throw(ArgumentError("toggles must be defined in block")) + presets_expr !== nothing || throw(ArgumentError("presets must be defined in block")) + groups_expr !== nothing || throw(ArgumentError("groups must be defined in block")) + + # Parse toggles - should be a tuple of symbols + toggles_expr.head == :tuple || throw(ArgumentError("toggles must be a tuple")) + # Extract the actual symbols from QuoteNode objects + toggles = [t.value for t in toggles_expr.args] + + # Parse presets - should be a NamedTuple + presets_expr.head == :tuple || throw(ArgumentError("presets must be a NamedTuple")) + presets_dict = Dict() + for preset_def in presets_expr.args + preset_def.head == :(=) || throw(ArgumentError("Each preset must be name = (...)")) + preset_name = preset_def.args[1] + preset_values = preset_def.args[2] + preset_values.head == :tuple || throw(ArgumentError("Preset values must be a NamedTuple")) + + # Parse the preset configuration + preset_config = Dict() + for toggle_def in preset_values.args + toggle_def.head == :(=) || throw(ArgumentError("Each toggle must be toggle_name = value")) + toggle_name = toggle_def.args[1] + toggle_value_expr = toggle_def.args[2] + preset_config[toggle_name] = toggle_value_expr + end + presets_dict[preset_name] = preset_config + end + + # Parse groups + groups_expr.head == :tuple || throw(ArgumentError("groups must be a NamedTuple")) + groups_dict = Dict() + for group_def in groups_expr.args + group_def.head == :(=) || throw(ArgumentError("Each group must be name = (...)")) + group_name = group_def.args[1] + group_toggles_expr = group_def.args[2] + group_toggles_expr.head == :tuple || throw(ArgumentError("Group toggles must be a tuple")) + # Extract the actual symbols from QuoteNode objects + group_toggles = [t isa QuoteNode ? t.value : t for t in group_toggles_expr.args] + groups_dict[group_name] = group_toggles + end + + # Now generate the code + preset_names = collect(keys(presets_dict)) + group_names = collect(keys(groups_dict)) + + # Standard presets that already exist + standard_presets = (:None, :Minimal, :Standard, :Detailed, :All) + custom_presets = filter(p -> !(p in standard_presets), preset_names) + + # Generate custom preset types + custom_preset_defs = [:(struct $p <: AbstractVerbosityPreset end) for p in custom_presets] + + # Generate parametric struct + type_params = [Symbol("T$i") for i in eachindex(toggles)] + struct_fields = [:($(toggles[i])::$(type_params[i])) for i in eachindex(toggles)] + + struct_def = :( + struct $name{$(type_params...)} <: AbstractVerbositySpecifier + $(struct_fields...) + end + ) + + # Generate preset constructors + preset_constructors = [] + for preset_name in preset_names + preset_config = presets_dict[preset_name] + field_values = [preset_config[t] for t in toggles] + + push!(preset_constructors, :( + function $name(::$preset_name) + return $name($(field_values...)) + end + )) + end + + # Build toggle to group mapping + toggle_to_group = Dict{Symbol, Union{Symbol, Nothing}}() + for toggle in toggles + toggle_to_group[toggle] = nothing + for (group_name, group_toggles) in groups_dict + if toggle in group_toggles + toggle_to_group[toggle] = group_name + break + end + end + end + + # Get Standard preset config for fast path + standard_config = presets_dict[:Standard] + fast_path_values = [standard_config[t] for t in toggles] + + # Build validation for groups + group_validations = [] + for group_name in group_names + push!(group_validations, quote + if $(group_name) !== nothing && !($(group_name) isa AbstractMessageLevel) + throw(ArgumentError("$($(QuoteNode(group_name))) must be a SciMLLogging.AbstractMessageLevel, got $(typeof($(group_name)))")) + end + end) + end + + # Build precedence logic for each toggle + toggle_assignments = [] + for toggle in toggles + group = toggle_to_group[toggle] + toggle_key = QuoteNode(toggle) + + if group === nothing + # Not in any group: individual > preset + # Build preset_config inline as a tuple access + push!(toggle_assignments, :($toggle = haskey(kwargs, $toggle_key) ? kwargs[$toggle_key] : preset_config[$toggle_key])) + else + # In a group: individual > group > preset + push!(toggle_assignments, :($toggle = haskey(kwargs, $toggle_key) ? kwargs[$toggle_key] : ($group !== nothing ? $group : preset_config[$toggle_key]))) + end + end + + # Build preset_map as a constant NamedTuple for runtime access + # For each preset, create a NamedTuple of its toggle configurations + preset_configs = [] + for pname in preset_names + toggle_values = [presets_dict[pname][t] for t in toggles] + # Use tuple of symbols for NamedTuple type parameter + push!(preset_configs, :(NamedTuple{$(Tuple(toggles))}(($(toggle_values...),)))) + end + + # Use tuple of symbols for preset names + preset_map_const = :(const $(Symbol("_preset_map_", name)) = NamedTuple{$(Tuple(preset_names))}(($(preset_configs...),))) + + # Build main constructor + kwarg_params = [Expr(:kw, :preset, :nothing); [Expr(:kw, g, :nothing) for g in group_names]] + runtime_condition = foldr((a, b) -> :($a && $b), [:($(g) === nothing) for g in group_names]; init = :(preset === nothing && isempty(kwargs))) + + main_constructor = quote + function $name(; $(kwarg_params...), kwargs...) + kwargs = NamedTuple(kwargs) + + # Fast path: all defaults + if $runtime_condition + return $name($(fast_path_values...)) + end + + # Validate groups + $(group_validations...) + + # Validate preset + if preset !== nothing && !(preset isa AbstractVerbosityPreset) + throw(ArgumentError("preset must be a SciMLLogging.AbstractVerbosityPreset, got $(typeof(preset))")) + end + + # Validate kwargs + for (key, value) in pairs(kwargs) + if !(key in $(Tuple(toggles))) + throw(ArgumentError("Unknown verbosity option: $key. Valid options are: $($(Tuple(toggles)))")) + end + if !(value isa AbstractMessageLevel) + throw(ArgumentError("$key must be a SciMLLogging.AbstractMessageLevel, got $(typeof(value))")) + end + end + + # Get preset config + preset_to_use = preset === nothing ? Standard() : preset + preset_config = $(Symbol("_preset_map_", name))[typeof(preset_to_use).name.name] + + # Apply precedence + $(toggle_assignments...) + + return $name($([t for t in toggles]...)) + end + end + + # Assemble everything + result = quote + $(custom_preset_defs...) + $preset_map_const + $struct_def + $(preset_constructors...) + $main_constructor + end + + return esc(result) +end diff --git a/test/generation_test.jl b/test/generation_test.jl new file mode 100644 index 0000000..71eca1e --- /dev/null +++ b/test/generation_test.jl @@ -0,0 +1,528 @@ +using SciMLLogging +using SciMLLogging: SciMLLogging, AbstractVerbositySpecifier, @SciMLMessage, + AbstractVerbosityPreset, AbstractMessageLevel, WarnLevel, InfoLevel, + ErrorLevel, Silent, None, All, Minimal, @verbosity_specifier +using Logging +using Test + +SciMLLogging.@verbosity_specifier VerbSpec begin + toggles = (:toggle1, :toggle2, :toggle3, :toggle4, :toggle5, + :toggle6, :toggle7, :toggle8, :toggle9, :toggle10) + + presets = ( + None = ( + toggle1 = Silent(), + toggle2 = Silent(), + toggle3 = Silent(), + toggle4 = Silent(), + toggle5 = Silent(), + toggle6 = Silent(), + toggle7 = Silent(), + toggle8 = Silent(), + toggle9 = Silent(), + toggle10 = Silent() + ), + Minimal = ( + toggle1 = WarnLevel(), + toggle2 = Silent(), + toggle3 = ErrorLevel(), + toggle4 = DebugLevel(), + toggle5 = InfoLevel(), + toggle6 = WarnLevel(), + toggle7 = Silent(), + toggle8 = InfoLevel(), + toggle9 = DebugLevel(), + toggle10 = ErrorLevel() + ), + Standard = ( + toggle1 = InfoLevel(), + toggle2 = WarnLevel(), + toggle3 = DebugLevel(), + toggle4 = ErrorLevel(), + toggle5 = Silent(), + toggle6 = InfoLevel(), + toggle7 = DebugLevel(), + toggle8 = WarnLevel(), + toggle9 = Silent(), + toggle10 = ErrorLevel() + ), + Detailed = ( + toggle1 = DebugLevel(), + toggle2 = InfoLevel(), + toggle3 = Silent(), + toggle4 = WarnLevel(), + toggle5 = ErrorLevel(), + toggle6 = DebugLevel(), + toggle7 = ErrorLevel(), + toggle8 = Silent(), + toggle9 = WarnLevel(), + toggle10 = InfoLevel() + ), + All = ( + toggle1 = ErrorLevel(), + toggle2 = DebugLevel(), + toggle3 = InfoLevel(), + toggle4 = Silent(), + toggle5 = WarnLevel(), + toggle6 = ErrorLevel(), + toggle7 = InfoLevel(), + toggle8 = DebugLevel(), + toggle9 = WarnLevel(), + toggle10 = Silent() + ) + ) + + groups = ( + numerical = (:toggle1, :toggle2, :toggle3), + performance = (:toggle4, :toggle5, :toggle6, :toggle7), + error_control = (:toggle8, :toggle9, :toggle10) + ) +end + +@testset "VerbSpec Constructor Tests" begin + # Test 1: Default constructor (no arguments) - should use Standard preset + @testset "Default constructor" begin + v = VerbSpec() + @test v.toggle1 == InfoLevel() + @test v.toggle2 == WarnLevel() + @test v.toggle3 == DebugLevel() + @test v.toggle10 == ErrorLevel() + end + + # Test 2: Preset constructors + @testset "Preset constructors" begin + # None preset + v_none = VerbSpec(None()) + @test all(getfield(v_none, f) == Silent() for f in fieldnames(typeof(v_none))) + + # Minimal preset + v_min = VerbSpec(Minimal()) + @test v_min.toggle1 == WarnLevel() + @test v_min.toggle2 == Silent() + @test v_min.toggle3 == ErrorLevel() + + # Standard preset + v_std = VerbSpec(Standard()) + @test v_std.toggle1 == InfoLevel() + @test v_std.toggle2 == WarnLevel() + + # Detailed preset + v_det = VerbSpec(Detailed()) + @test v_det.toggle1 == DebugLevel() + @test v_det.toggle2 == InfoLevel() + + # All preset + v_all = VerbSpec(All()) + @test v_all.toggle1 == ErrorLevel() + @test v_all.toggle2 == DebugLevel() + end + + # Test 3: Keyword constructor with preset parameter + @testset "Keyword constructor with preset" begin + v = VerbSpec(preset=Minimal()) + @test v.toggle1 == WarnLevel() + @test v.toggle2 == Silent() + end + + # Test 4: Group parameters + @testset "Group parameters" begin + # Set all numerical toggles to ErrorLevel + v = VerbSpec(numerical=ErrorLevel()) + @test v.toggle1 == ErrorLevel() # numerical group + @test v.toggle2 == ErrorLevel() # numerical group + @test v.toggle3 == ErrorLevel() # numerical group + @test v.toggle4 == ErrorLevel() # performance group (from Standard preset) + + # Set all performance toggles + v2 = VerbSpec(performance=InfoLevel()) + @test v2.toggle4 == InfoLevel() # performance group + @test v2.toggle5 == InfoLevel() # performance group + @test v2.toggle6 == InfoLevel() # performance group + @test v2.toggle7 == InfoLevel() # performance group + + # Set all error_control toggles + v3 = VerbSpec(error_control=DebugLevel()) + @test v3.toggle8 == DebugLevel() # error_control group + @test v3.toggle9 == DebugLevel() # error_control group + @test v3.toggle10 == DebugLevel() # error_control group + end + + # Test 5: Individual toggle parameters + @testset "Individual toggle parameters" begin + v = VerbSpec(toggle1=ErrorLevel()) + @test v.toggle1 == ErrorLevel() + @test v.toggle2 == WarnLevel() # from Standard preset + + # Multiple individual toggles + v2 = VerbSpec(toggle1=ErrorLevel(), toggle5=WarnLevel()) + @test v2.toggle1 == ErrorLevel() + @test v2.toggle5 == WarnLevel() + end + + # Test 6: Precedence: individual > group > preset + @testset "Precedence tests" begin + # Individual overrides group + v = VerbSpec(numerical=WarnLevel(), toggle1=ErrorLevel()) + @test v.toggle1 == ErrorLevel() # individual wins + @test v.toggle2 == WarnLevel() # from group + @test v.toggle3 == WarnLevel() # from group + + # Group overrides preset + v2 = VerbSpec(preset=None(), numerical=InfoLevel()) + @test v2.toggle1 == InfoLevel() # from group + @test v2.toggle4 == Silent() # from preset (not in numerical group) + + # All three levels + v3 = VerbSpec(preset=None(), performance=WarnLevel(), toggle4=ErrorLevel()) + @test v3.toggle4 == ErrorLevel() # individual wins + @test v3.toggle5 == WarnLevel() # from group + @test v3.toggle1 == Silent() # from preset + end + + # Test 7: Combining preset with groups + @testset "Preset with groups" begin + v = VerbSpec(preset=Minimal(), numerical=DebugLevel()) + @test v.toggle1 == DebugLevel() # from group + @test v.toggle2 == DebugLevel() # from group + @test v.toggle3 == DebugLevel() # from group + @test v.toggle4 == DebugLevel() # from Minimal preset + end + + # Test 8: All three: preset, groups, and individual toggles + @testset "Preset + groups + individual toggles" begin + v = VerbSpec( + preset=Detailed(), + numerical=ErrorLevel(), + performance=WarnLevel(), + toggle1=InfoLevel(), + toggle4=DebugLevel() + ) + @test v.toggle1 == InfoLevel() # individual wins + @test v.toggle2 == ErrorLevel() # from numerical group + @test v.toggle3 == ErrorLevel() # from numerical group + @test v.toggle4 == DebugLevel() # individual wins + @test v.toggle5 == WarnLevel() # from performance group + @test v.toggle8 == Silent() # from Detailed preset + end + + # Test 9: Type stability - ensure parametric types work + @testset "Type stability" begin + v = VerbSpec() + @test typeof(v) <: VerbSpec + @test isconcretetype(typeof(v)) + end + + # Test 10: Error handling + @testset "Error handling" begin + # Invalid group argument type + @test_throws ArgumentError VerbSpec(numerical="invalid") + + # Invalid individual toggle type + @test_throws ArgumentError VerbSpec(toggle1="invalid") + + # Unknown toggle name + @test_throws ArgumentError VerbSpec(unknown_toggle=ErrorLevel()) + + # Invalid preset type + @test_throws ArgumentError VerbSpec(preset="invalid") + end +end + + + +# LinearVerbosity configuration +@verbosity_specifier LinearVerbosity begin + toggles = ( + :default_lu_fallback, + :no_right_preconditioning, + :using_IterativeSolvers, + :IterativeSolvers_iterations, + :KrylovKit_verbosity, + :KrylovJL_verbosity, + :HYPRE_verbosity, + :pardiso_verbosity, + :blas_errors, + :blas_invalid_args, + :blas_info, + :blas_success, + :condition_number, + :convergence_failure, + :solver_failure, + :max_iters + ) + + presets = ( + None = ( + default_lu_fallback = Silent(), + no_right_preconditioning = Silent(), + using_IterativeSolvers = Silent(), + IterativeSolvers_iterations = Silent(), + KrylovKit_verbosity = Silent(), + KrylovJL_verbosity = Silent(), + HYPRE_verbosity = Silent(), + pardiso_verbosity = Silent(), + blas_errors = Silent(), + blas_invalid_args = Silent(), + blas_info = Silent(), + blas_success = Silent(), + condition_number = Silent(), + convergence_failure = Silent(), + solver_failure = Silent(), + max_iters = Silent() + ), + Minimal = ( + default_lu_fallback = Silent(), + no_right_preconditioning = Silent(), + using_IterativeSolvers = Silent(), + IterativeSolvers_iterations = Silent(), + KrylovKit_verbosity = Silent(), + KrylovJL_verbosity = Silent(), + HYPRE_verbosity = Silent(), + pardiso_verbosity = Silent(), + blas_errors = WarnLevel(), + blas_invalid_args = WarnLevel(), + blas_info = Silent(), + blas_success = Silent(), + condition_number = Silent(), + convergence_failure = Silent(), + solver_failure = Silent(), + max_iters = Silent() + ), + Standard = ( + default_lu_fallback = Silent(), + no_right_preconditioning = Silent(), + using_IterativeSolvers = Silent(), + IterativeSolvers_iterations = Silent(), + KrylovKit_verbosity = CustomLevel(1), + KrylovJL_verbosity = Silent(), + HYPRE_verbosity = InfoLevel(), + pardiso_verbosity = Silent(), + blas_errors = WarnLevel(), + blas_invalid_args = WarnLevel(), + blas_info = Silent(), + blas_success = Silent(), + condition_number = Silent(), + convergence_failure = WarnLevel(), + solver_failure = WarnLevel(), + max_iters = WarnLevel() + ), + Detailed = ( + default_lu_fallback = WarnLevel(), + no_right_preconditioning = InfoLevel(), + using_IterativeSolvers = InfoLevel(), + IterativeSolvers_iterations = Silent(), + KrylovKit_verbosity = CustomLevel(2), + KrylovJL_verbosity = CustomLevel(1), + HYPRE_verbosity = InfoLevel(), + pardiso_verbosity = CustomLevel(1), + blas_errors = WarnLevel(), + blas_invalid_args = WarnLevel(), + blas_info = InfoLevel(), + blas_success = InfoLevel(), + condition_number = Silent(), + convergence_failure = WarnLevel(), + solver_failure = WarnLevel(), + max_iters = WarnLevel() + ), + All = ( + default_lu_fallback = WarnLevel(), + no_right_preconditioning = InfoLevel(), + using_IterativeSolvers = InfoLevel(), + IterativeSolvers_iterations = InfoLevel(), + KrylovKit_verbosity = CustomLevel(3), + KrylovJL_verbosity = CustomLevel(1), + HYPRE_verbosity = InfoLevel(), + pardiso_verbosity = CustomLevel(1), + blas_errors = WarnLevel(), + blas_invalid_args = WarnLevel(), + blas_info = InfoLevel(), + blas_success = InfoLevel(), + condition_number = InfoLevel(), + convergence_failure = WarnLevel(), + solver_failure = WarnLevel(), + max_iters = WarnLevel() + ) + ) + + groups = ( + error_control = (:default_lu_fallback, :blas_errors, :blas_invalid_args), + performance = (:no_right_preconditioning,), + numerical = (:using_IterativeSolvers, :IterativeSolvers_iterations, + :KrylovKit_verbosity, :KrylovJL_verbosity, :HYPRE_verbosity, + :pardiso_verbosity, :blas_info, :blas_success, :condition_number, + :convergence_failure, :solver_failure, :max_iters) + ) +end + +# Test LinearVerbosity +@testset "LinearVerbosity Tests" begin + @testset "Default constructor" begin + v = LinearVerbosity() + @test v.blas_errors == WarnLevel() + @test v.default_lu_fallback == Silent() + @test v.KrylovKit_verbosity == CustomLevel(1) + end + + @testset "Preset constructors" begin + v_none = LinearVerbosity(None()) + @test all(getfield(v_none, f) == Silent() for f in fieldnames(typeof(v_none))) + + v_min = LinearVerbosity(Minimal()) + @test v_min.blas_errors == WarnLevel() + @test v_min.convergence_failure == Silent() + end + + @testset "Group parameters" begin + v = LinearVerbosity(error_control=ErrorLevel()) + @test v.default_lu_fallback == ErrorLevel() + @test v.blas_errors == ErrorLevel() + @test v.blas_invalid_args == ErrorLevel() + end +end + +# Test with preset types as toggle values (useful for verb specs that hold other verb specs, e.g. NonlinearVerbosity) +@verbosity_specifier HierarchicalVerbosity begin + toggles = (:component_a, :component_b, :component_c) + + presets = ( + None = ( + component_a = Silent(), + component_b = Silent(), + component_c = Silent() + ), + Minimal = ( + component_a = WarnLevel(), + component_b = Minimal(), # Reference to another preset + component_c = InfoLevel() + ), + Standard = ( + component_a = InfoLevel(), + component_b = Standard(), # Reference to another preset + component_c = WarnLevel() + ), + Detailed = ( + component_a = DebugLevel(), + component_b = Detailed(), # Reference to another preset + component_c = InfoLevel() + ), + All = ( + component_a = ErrorLevel(), + component_b = All(), # Reference to another preset + component_c = DebugLevel() + ) + ) + + groups = ( + main = (:component_a,), + auxiliary = (:component_b, :component_c) + ) +end + +@testset "HierarchicalVerbosity with Preset References" begin + @testset "Preset as toggle value" begin + v = HierarchicalVerbosity(preset=Minimal()) + @test v.component_a == WarnLevel() + @test v.component_b == Minimal() # Should be the preset type, not expanded + @test v.component_c == InfoLevel() + + v2 = HierarchicalVerbosity(preset=Standard()) + @test v2.component_a == InfoLevel() + @test v2.component_b == Standard() + @test v2.component_c == WarnLevel() + end + + @testset "Override preset-typed toggle" begin + # Override a preset-typed toggle with a message level + v = HierarchicalVerbosity(preset=Standard(), component_b=ErrorLevel()) + @test v.component_a == InfoLevel() + @test v.component_b == ErrorLevel() # Overridden + @test v.component_c == WarnLevel() + end +end + +# Test with custom presets +@verbosity_specifier CustomPresetVerbosity begin + toggles = (:solver, :preconditioner, :convergence) + + presets = ( + None = ( + solver = Silent(), + preconditioner = Silent(), + convergence = Silent() + ), + Minimal = ( + solver = WarnLevel(), + preconditioner = Silent(), + convergence = WarnLevel() + ), + Standard = ( + solver = InfoLevel(), + preconditioner = InfoLevel(), + convergence = WarnLevel() + ), + Detailed = ( + solver = DebugLevel(), + preconditioner = InfoLevel(), + convergence = InfoLevel() + ), + All = ( + solver = DebugLevel(), + preconditioner = DebugLevel(), + convergence = DebugLevel() + ), + # Custom presets + QuietSolver = ( + solver = Silent(), + preconditioner = InfoLevel(), + convergence = WarnLevel() + ), + VerboseSolver = ( + solver = DebugLevel(), + preconditioner = Silent(), + convergence = ErrorLevel() + ) + ) + + groups = ( + core = (:solver, :preconditioner), + diagnostics = (:convergence,) + ) +end + +@testset "CustomPresetVerbosity with Custom Presets" begin + @testset "Standard presets work" begin + v = CustomPresetVerbosity(preset=Standard()) + @test v.solver == InfoLevel() + @test v.preconditioner == InfoLevel() + @test v.convergence == WarnLevel() + end + + @testset "Custom preset QuietSolver" begin + v = CustomPresetVerbosity(preset=QuietSolver()) + @test v.solver == Silent() + @test v.preconditioner == InfoLevel() + @test v.convergence == WarnLevel() + end + + @testset "Custom preset VerboseSolver" begin + v = CustomPresetVerbosity(preset=VerboseSolver()) + @test v.solver == DebugLevel() + @test v.preconditioner == Silent() + @test v.convergence == ErrorLevel() + end + + @testset "Override custom preset with group" begin + v = CustomPresetVerbosity(preset=QuietSolver(), core=ErrorLevel()) + @test v.solver == ErrorLevel() # Overridden by group + @test v.preconditioner == ErrorLevel() # Overridden by group + @test v.convergence == WarnLevel() # From preset + end + + @testset "Override custom preset with individual toggle" begin + v = CustomPresetVerbosity(preset=VerboseSolver(), solver=InfoLevel()) + @test v.solver == InfoLevel() # Overridden + @test v.preconditioner == Silent() # From preset + @test v.convergence == ErrorLevel() # From preset + end +end + diff --git a/test/runtests.jl b/test/runtests.jl index 90b4d58..a085435 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,5 @@ -using SciMLLogging using Test using SafeTestsets @time @safetestset "Basic Tests" include("basics.jl") +@time @safetestset "Verbosity Specifier Generation Tests" include("generation_test.jl") \ No newline at end of file