NickelEval.jl

Julia FFI bindings for Nickel configuration language
Log | Files | Refs | README | LICENSE

test_lazy.jl (7375B)


      1 @testset "Lazy Evaluation" begin
      2     @testset "nickel_open returns NickelValue" begin
      3         is_nickel_value = nickel_open("{ x = 1 }") do cfg
      4             cfg isa NickelValue
      5         end
      6         @test is_nickel_value
      7     end
      8 
      9     @testset "nickel_kind" begin
     10         nickel_open("{ x = 1 }") do cfg
     11             @test nickel_kind(cfg) == :record
     12         end
     13         nickel_open("[1, 2, 3]") do cfg
     14             @test nickel_kind(cfg) == :array
     15         end
     16         nickel_open("42") do cfg
     17             @test nickel_kind(cfg) == :number
     18         end
     19         nickel_open("\"hello\"") do cfg
     20             @test nickel_kind(cfg) == :string
     21         end
     22         nickel_open("true") do cfg
     23             @test nickel_kind(cfg) == :bool
     24         end
     25         nickel_open("null") do cfg
     26             @test nickel_kind(cfg) == :null
     27         end
     28     end
     29 
     30     @testset "Record field access" begin
     31         # getproperty (dot syntax)
     32         nickel_open("{ x = 42 }") do cfg
     33             @test cfg.x === Int64(42)
     34         end
     35 
     36         # getindex (bracket syntax)
     37         nickel_open("{ x = 42 }") do cfg
     38             @test cfg["x"] === Int64(42)
     39         end
     40 
     41         # Nested navigation
     42         nickel_open("{ a = { b = { c = 99 } } }") do cfg
     43             @test cfg.a.b.c === Int64(99)
     44         end
     45 
     46         # Mixed types in record
     47         nickel_open("{ name = \"test\", count = 42, flag = true }") do cfg
     48             @test cfg.name == "test"
     49             @test cfg.count === Int64(42)
     50             @test cfg.flag === true
     51         end
     52 
     53         # Null field
     54         nickel_open("{ x = null }") do cfg
     55             @test cfg.x === nothing
     56         end
     57     end
     58 
     59     @testset "Array access" begin
     60         nickel_open("[10, 20, 30]") do cfg
     61             @test cfg[1] === Int64(10)
     62             @test cfg[2] === Int64(20)
     63             @test cfg[3] === Int64(30)
     64         end
     65 
     66         # Array of records (lazy)
     67         nickel_open("[{ x = 1 }, { x = 2 }]") do cfg
     68             @test cfg[1].x === Int64(1)
     69             @test cfg[2].x === Int64(2)
     70         end
     71 
     72         # Nested: record containing array
     73         nickel_open("{ items = [10, 20, 30] }") do cfg
     74             @test cfg.items[2] === Int64(20)
     75         end
     76     end
     77 
     78     @testset "collect" begin
     79         # Record
     80         nickel_open("{ a = 1, b = \"two\", c = true }") do cfg
     81             result = collect(cfg)
     82             @test result isa Dict{String, Any}
     83             @test result == nickel_eval("{ a = 1, b = \"two\", c = true }")
     84         end
     85 
     86         # Nested record
     87         nickel_open("{ x = { y = 42 } }") do cfg
     88             result = collect(cfg)
     89             @test result["x"]["y"] === Int64(42)
     90         end
     91 
     92         # Array
     93         nickel_open("[1, 2, 3]") do cfg
     94             result = collect(cfg)
     95             @test result == Any[1, 2, 3]
     96         end
     97 
     98         # Collect a sub-tree
     99         nickel_open("{ a = 1, b = { c = 2, d = 3 } }") do cfg
    100             sub = collect(cfg.b)
    101             @test sub == Dict{String, Any}("c" => 2, "d" => 3)
    102         end
    103 
    104         # Primitive passthrough
    105         nickel_open("42") do cfg
    106             @test collect(cfg) === Int64(42)
    107         end
    108     end
    109 
    110     @testset "keys and length" begin
    111         nickel_open("{ a = 1, b = 2, c = 3 }") do cfg
    112             k = keys(cfg)
    113             @test k isa Vector{String}
    114             @test sort(k) == ["a", "b", "c"]
    115             @test length(cfg) == 3
    116         end
    117 
    118         nickel_open("[10, 20, 30, 40]") do cfg
    119             @test length(cfg) == 4
    120         end
    121 
    122         # keys on non-record throws
    123         nickel_open("[1, 2]") do cfg
    124             @test_throws ArgumentError keys(cfg)
    125         end
    126     end
    127 
    128     @testset "File evaluation" begin
    129         mktempdir() do dir
    130             # Simple file
    131             f = joinpath(dir, "config.ncl")
    132             write(f, "{ host = \"localhost\", port = 8080 }")
    133             nickel_open(f) do cfg
    134                 @test cfg.host == "localhost"
    135                 @test cfg.port === Int64(8080)
    136             end
    137 
    138             # File with imports
    139             shared = joinpath(dir, "shared.ncl")
    140             write(shared, "{ version = \"1.0\" }")
    141             main = joinpath(dir, "main.ncl")
    142             write(main, """
    143 let s = import "shared.ncl" in
    144 { app_version = s.version, name = "myapp" }
    145 """)
    146             nickel_open(main) do cfg
    147                 @test cfg.app_version == "1.0"
    148                 @test cfg.name == "myapp"
    149             end
    150         end
    151     end
    152 
    153     @testset "Iteration" begin
    154         # Record iteration yields pairs
    155         nickel_open("{ a = 1, b = 2 }") do cfg
    156             pairs = Dict(k => v for (k, v) in cfg)
    157             @test pairs["a"] === Int64(1)
    158             @test pairs["b"] === Int64(2)
    159         end
    160 
    161         # Array iteration
    162         nickel_open("[10, 20, 30]") do cfg
    163             values = [x for x in cfg]
    164             @test values == Any[10, 20, 30]
    165         end
    166     end
    167 
    168     @testset "Error handling" begin
    169         # Closed session
    170         local stale_ref
    171         nickel_open("{ x = 1 }") do cfg
    172             stale_ref = cfg
    173         end
    174         @test_throws ArgumentError stale_ref.x
    175 
    176         # Wrong access type: dot on array
    177         nickel_open("[1, 2, 3]") do cfg
    178             @test_throws ArgumentError cfg.x
    179         end
    180 
    181         # Wrong access type: integer index on record
    182         nickel_open("{ x = 1 }") do cfg
    183             @test_throws ArgumentError cfg[1]
    184         end
    185 
    186         # Out of bounds
    187         nickel_open("[1, 2]") do cfg
    188             @test_throws BoundsError cfg[3]
    189         end
    190 
    191         # Syntax error in code
    192         @test_throws NickelError nickel_open("{ x = }")
    193     end
    194 
    195     @testset "Enum handling" begin
    196         # Bare enum tag at top level: nickel_open returns NickelValue, collect to get NickelEnum
    197         nickel_open("let x = 'Foo in x") do cfg
    198             @test cfg isa NickelValue
    199             @test nickel_kind(cfg) == :enum
    200             result = collect(cfg)
    201             @test result isa NickelEnum
    202             @test result.tag == :Foo
    203         end
    204 
    205         # Enum variant at top level
    206         nickel_open("let x = 'Some 42 in x") do cfg
    207             result = collect(cfg)
    208             @test result isa NickelEnum
    209             @test result.tag == :Some
    210             @test result.arg === Int64(42)
    211         end
    212 
    213         # Enum as record field: resolved immediately by field access
    214         nickel_open("{ x = 'None, y = 'Some 42 }") do cfg
    215             @test cfg.x isa NickelEnum
    216             @test cfg.x.tag == :None
    217         end
    218 
    219         # Enum in collect
    220         nickel_open("{ x = 'None, y = 'Some 42 }") do cfg
    221             result = collect(cfg)
    222             @test result["x"] isa NickelEnum
    223             @test result["x"].tag == :None
    224             @test result["y"] isa NickelEnum
    225             @test result["y"].tag == :Some
    226         end
    227     end
    228 
    229     @testset "Manual session" begin
    230         cfg = nickel_open("{ x = 42, y = \"hello\" }")
    231         @test cfg.x === Int64(42)
    232         @test cfg.y == "hello"
    233         close(cfg)
    234 
    235         # Double close is safe
    236         close(cfg)
    237 
    238         # Access after close throws
    239         @test_throws ArgumentError cfg.x
    240     end
    241 
    242     @testset "show" begin
    243         nickel_open("{ x = 1, y = 2, z = 3 }") do cfg
    244             s = repr(cfg)
    245             @test occursin("record", s)
    246             @test occursin("3", s)
    247         end
    248         nickel_open("[1, 2]") do cfg
    249             s = repr(cfg)
    250             @test occursin("array", s)
    251             @test occursin("2", s)
    252         end
    253     end
    254 end