From 30afe08b5d4af597588551e132c67a51974557a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8egh?= Date: Sat, 13 Aug 2016 13:17:43 +0200 Subject: [PATCH 1/5] Move env.jl test to separate test file --- test/choosetests.jl | 2 +- test/env.jl | 32 ++++++++++++++++++++++++++++++++ test/sysinfo.jl | 31 ------------------------------- 3 files changed, 33 insertions(+), 32 deletions(-) create mode 100644 test/env.jl diff --git a/test/choosetests.jl b/test/choosetests.jl index d02ed1dcf3cf0..f15e61a139d6c 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -26,7 +26,7 @@ function choosetests(choices = []) "priorityqueue", "file", "read", "mmap", "version", "resolve", "pollfd", "mpfr", "broadcast", "complex", "socket", "floatapprox", "datafmt", "reflection", "regex", "float16", - "combinatorics", "sysinfo", "rounding", "ranges", "mod2pi", + "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", "euler", "show", "lineedit", "replcompletions", "repl", "replutil", "sets", "test", "goto", "llvmcall", "grisu", "nullable", "meta", "stacktraces", "profile", "libgit2", "docs", diff --git a/test/env.jl b/test/env.jl new file mode 100644 index 0000000000000..592c378cf65ee --- /dev/null +++ b/test/env.jl @@ -0,0 +1,32 @@ +# This file is a part of Julia. License is MIT: http://julialang.org/license + +@test !("f=a=k=e=n=a=m=e" ∈ keys(ENV)) + +# issue #10994 +@test_throws ArgumentError ENV["bad\0name"] = "ok" +@test_throws ArgumentError ENV["okname"] = "bad\0val" +@test_throws ArgumentError Sys.set_process_title("bad\0title") + +withenv("bad"=>"dog") do + @test_throws ArgumentError ENV["bad\0cat"] +end + +# issue #11170 +withenv("TEST"=>"nonempty") do + @test ENV["TEST"] == "nonempty" +end +withenv("TEST"=>"") do + @test ENV["TEST"] == "" +end + +let c = collect(ENV) + @test isa(c, Vector) + @test length(ENV) == length(c) + @test isempty(ENV) || first(ENV) in c +end + +# test for non-existent keys +key = randstring(25) +@test !haskey(ENV,key) +@test_throws KeyError ENV[key] +@test get(ENV,key,"default") == "default" diff --git a/test/sysinfo.jl b/test/sysinfo.jl index 645c34efce765..261935ac715f1 100644 --- a/test/sysinfo.jl +++ b/test/sysinfo.jl @@ -6,34 +6,3 @@ sprint(Base.Sys.cpu_summary) @test Base.Sys.uptime() > 0 Base.Sys.loadavg() - -@test !("f=a=k=e=n=a=m=e" ∈ keys(ENV)) - -# issue #10994 -@test_throws ArgumentError ENV["bad\0name"] = "ok" -@test_throws ArgumentError ENV["okname"] = "bad\0val" -@test_throws ArgumentError Sys.set_process_title("bad\0title") - -withenv("bad"=>"dog") do - @test_throws ArgumentError ENV["bad\0cat"] -end - -# issue #11170 -withenv("TEST"=>"nonempty") do - @test ENV["TEST"] == "nonempty" -end -withenv("TEST"=>"") do - @test ENV["TEST"] == "" -end - -let c = collect(ENV) - @test isa(c, Vector) - @test length(ENV) == length(c) - @test isempty(ENV) || first(ENV) in c -end - -# test for non-existent keys -key = randstring(25) -@test !haskey(ENV,key) -@test_throws KeyError ENV[key] -@test get(ENV,key,"default") == "default" From b1e82163e259305ed989b005a736f941bf183f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8egh?= Date: Sat, 13 Aug 2016 13:20:09 +0200 Subject: [PATCH 2/5] Fix #17956 and add test. The bug was introduced in 103db50aa6c67d8e732e8e2bd7f5743bae17e369 --- base/env.jl | 2 +- test/env.jl | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/base/env.jl b/base/env.jl index fc3fe87e78c70..3c77619400b82 100644 --- a/base/env.jl +++ b/base/env.jl @@ -94,7 +94,7 @@ end function next(hash::EnvHash, block::Tuple{Ptr{UInt16},Ptr{UInt16}}) pos = block[1] blk = block[2] - len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos) + len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos) + 1 buf = Array{UInt16}(len) unsafe_copy!(pointer(buf), pos, len) env = transcode(String, buf) diff --git a/test/env.jl b/test/env.jl index 592c378cf65ee..23e8710398cab 100644 --- a/test/env.jl +++ b/test/env.jl @@ -30,3 +30,36 @@ key = randstring(25) @test !haskey(ENV,key) @test_throws KeyError ENV[key] @test get(ENV,key,"default") == "default" + +# Test for #17956 +@test length(ENV) > 1 +k1, k2 = "__test__", "__test1__" +withenv(k1=>k1, k2=>k2) do + b_k1, b_k2 = false, false + for (k, v) in ENV + if k==k1 + b_k1=true + elseif k==k2 + b_k2=true + end + end + @test b_k1 && b_k2 + io = IOBuffer() + show(io, ENV) + s = takebuf_string(io) + @test contains(s, "$k1=$k1") + @test contains(s, "$k2=$k2") + + @test pop!(ENV, k1) == k1 + @test !haskey(ENV, k1) + ENV[k1] = k1 + @test pop!(ENV, k1) == k1 + @test pop!(ENV, k1, "not_there") == "not_there" + + ENV[k1] = k1 + @test delete!(ENV, k1) == ENV + @test !haskey(ENV, k1) +end + +# Test for #10853 +@test withenv(Dict{Any,Any}()...) do; true; end From d0b06a30f3486e3e48ce11abe618815bbd11901f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8egh?= Date: Sat, 13 Aug 2016 13:46:06 +0200 Subject: [PATCH 3/5] Deprecate `delete!(ENV, k ,def)` as it is inherently type unstable and no other associative type implement the method. --- base/deprecated.jl | 5 +++++ base/env.jl | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/base/deprecated.jl b/base/deprecated.jl index 7dffd61acc9ba..6b3360200814d 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -785,4 +785,9 @@ const _oldstyle_array_vcat_ = false @deprecate write(x) write(STDOUT::IO, x) +function delete!(::EnvHash, k::AbstractString, def) + depwarn("`delete!(ENV, k, def)` should be replaced with `pop!(ENV, k, def)`. Be aware that `pop!` returns `k` or `def`, while `delete!` returns `ENV` or `def`.", :delete!) + haskey(ENV,k) ? delete!(ENV,k) : def +end + # End deprecations scheduled for 0.6 diff --git a/base/env.jl b/base/env.jl index 3c77619400b82..893191d015678 100644 --- a/base/env.jl +++ b/base/env.jl @@ -78,7 +78,6 @@ in(k::AbstractString, ::KeyIterator{EnvHash}) = _hasenv(k) pop!(::EnvHash, k::AbstractString) = (v = ENV[k]; _unsetenv(k); v) pop!(::EnvHash, k::AbstractString, def) = haskey(ENV,k) ? pop!(ENV,k) : def delete!(::EnvHash, k::AbstractString) = (_unsetenv(k); ENV) -delete!(::EnvHash, k::AbstractString, def) = haskey(ENV,k) ? delete!(ENV,k) : def setindex!(::EnvHash, v, k::AbstractString) = _setenv(k,string(v)) push!(::EnvHash, k::AbstractString, v) = setindex!(ENV, v, k) From bb7272ed6556ec19ffc606eb195b0bcf65bfa503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8egh?= Date: Sat, 13 Aug 2016 13:50:12 +0200 Subject: [PATCH 4/5] move doc for `withenv` --- base/docs/helpdb/Base.jl | 11 ----------- base/env.jl | 10 +++++++++- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index 3a669992c71f3..7bf14198490a1 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -2127,17 +2127,6 @@ Largest integer less than or equal to `x/y`. """ fld -""" - withenv(f::Function, kv::Pair...) - -Execute `f()` in an environment that is temporarily modified (not replaced as in `setenv`) -by zero or more `"var"=>val` arguments `kv`. `withenv` is generally used via the -`withenv(kv...) do ... end` syntax. A value of `nothing` can be used to temporarily unset an -environment variable (if it is set). When `withenv` returns, the original environment has -been restored. -""" -withenv - """ setdiff!(s, iterable) diff --git a/base/env.jl b/base/env.jl index 893191d015678..351ef42680d0b 100644 --- a/base/env.jl +++ b/base/env.jl @@ -138,7 +138,15 @@ function show(io::IO, ::EnvHash) end end -# temporarily set and then restore an environment value +""" + withenv(f::Function, kv::Pair...) + +Execute `f()` in an environment that is temporarily modified (not replaced as in `setenv`) +by zero or more `"var"=>val` arguments `kv`. `withenv` is generally used via the +`withenv(kv...) do ... end` syntax. A value of `nothing` can be used to temporarily unset an +environment variable (if it is set). When `withenv` returns, the original environment has +been restored. +""" function withenv{T<:AbstractString}(f::Function, keyvals::Pair{T}...) old = Dict{T,Any}() for (key,val) in keyvals From 319cae5641b4dec8af9697d568313577caf95fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8egh?= Date: Sun, 14 Aug 2016 12:51:18 +0200 Subject: [PATCH 5/5] Formatting: indent the OS specific blocks. --- base/env.jl | 152 +++++++++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 78 deletions(-) diff --git a/base/env.jl b/base/env.jl index 351ef42680d0b..78e2e0515145a 100644 --- a/base/env.jl +++ b/base/env.jl @@ -1,61 +1,59 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license if is_windows() -const ERROR_ENVVAR_NOT_FOUND = UInt32(203) + const ERROR_ENVVAR_NOT_FOUND = UInt32(203) -_getenvlen(var::Vector{UInt16}) = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,C_NULL,0) -_hasenv(s::Vector{UInt16}) = _getenvlen(s) != 0 || Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND -_hasenv(s::AbstractString) = _hasenv(cwstring(s)) + _getenvlen(var::Vector{UInt16}) = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,C_NULL,0) + _hasenv(s::Vector{UInt16}) = _getenvlen(s) != 0 || Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND + _hasenv(s::AbstractString) = _hasenv(cwstring(s)) -function access_env(onError::Function, str::AbstractString) - var = cwstring(str) - len = _getenvlen(var) - if len == 0 - return Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND ? "" : onError(str) + function access_env(onError::Function, str::AbstractString) + var = cwstring(str) + len = _getenvlen(var) + if len == 0 + return Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND ? "" : onError(str) + end + val = zeros(UInt16,len) + ret = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,val,len) + if (ret == 0 && len != 1) || ret != len-1 || val[end] != 0 + error(string("getenv: ", str, ' ', len, "-1 != ", ret, ": ", Libc.FormatMessage())) + end + pop!(val) # NUL + return transcode(String, val) end - val = zeros(UInt16,len) - ret = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,val,len) - if (ret == 0 && len != 1) || ret != len-1 || val[end] != 0 - error(string("getenv: ", str, ' ', len, "-1 != ", ret, ": ", Libc.FormatMessage())) + + function _setenv(svar::AbstractString, sval::AbstractString, overwrite::Bool=true) + var = cwstring(svar) + val = cwstring(sval) + if overwrite || !_hasenv(var) + ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,val) + systemerror(:setenv, ret == 0) + end end - pop!(val) # NUL - return transcode(String, val) -end -function _setenv(svar::AbstractString, sval::AbstractString, overwrite::Bool=true) - var = cwstring(svar) - val = cwstring(sval) - if overwrite || !_hasenv(var) - ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,val) + function _unsetenv(svar::AbstractString) + var = cwstring(svar) + ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,C_NULL) systemerror(:setenv, ret == 0) end -end - -function _unsetenv(svar::AbstractString) - var = cwstring(svar) - ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,C_NULL) - systemerror(:setenv, ret == 0) -end - else # !windows -_getenv(var::AbstractString) = ccall(:getenv, Cstring, (Cstring,), var) -_hasenv(s::AbstractString) = _getenv(s) != C_NULL - -function access_env(onError::Function, var::AbstractString) - val = _getenv(var) - val == C_NULL ? onError(var) : unsafe_string(val) -end + _getenv(var::AbstractString) = ccall(:getenv, Cstring, (Cstring,), var) + _hasenv(s::AbstractString) = _getenv(s) != C_NULL -function _setenv(var::AbstractString, val::AbstractString, overwrite::Bool=true) - ret = ccall(:setenv, Int32, (Cstring,Cstring,Int32), var, val, overwrite) - systemerror(:setenv, ret != 0) -end + function access_env(onError::Function, var::AbstractString) + val = _getenv(var) + val == C_NULL ? onError(var) : unsafe_string(val) + end -function _unsetenv(var::AbstractString) - ret = ccall(:unsetenv, Int32, (Cstring,), var) - systemerror(:unsetenv, ret != 0) -end + function _setenv(var::AbstractString, val::AbstractString, overwrite::Bool=true) + ret = ccall(:setenv, Int32, (Cstring,Cstring,Int32), var, val, overwrite) + systemerror(:setenv, ret != 0) + end + function _unsetenv(var::AbstractString) + ret = ccall(:unsetenv, Int32, (Cstring,), var) + systemerror(:unsetenv, ret != 0) + end end # os test ## ENV: hash interface ## @@ -82,45 +80,43 @@ setindex!(::EnvHash, v, k::AbstractString) = _setenv(k,string(v)) push!(::EnvHash, k::AbstractString, v) = setindex!(ENV, v, k) if is_windows() -start(hash::EnvHash) = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos)) -function done(hash::EnvHash, block::Tuple{Ptr{UInt16},Ptr{UInt16}}) - if unsafe_load(block[1]) == 0 - ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), block[2]) - return true + start(hash::EnvHash) = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos)) + function done(hash::EnvHash, block::Tuple{Ptr{UInt16},Ptr{UInt16}}) + if unsafe_load(block[1]) == 0 + ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), block[2]) + return true + end + return false end - return false -end -function next(hash::EnvHash, block::Tuple{Ptr{UInt16},Ptr{UInt16}}) - pos = block[1] - blk = block[2] - len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos) + 1 - buf = Array{UInt16}(len) - unsafe_copy!(pointer(buf), pos, len) - env = transcode(String, buf) - m = match(r"^(=?[^=]+)=(.*)$"s, env) - if m === nothing - error("malformed environment entry: $env") + function next(hash::EnvHash, block::Tuple{Ptr{UInt16},Ptr{UInt16}}) + pos = block[1] + blk = block[2] + len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos) + 1 + buf = Array{UInt16}(len) + unsafe_copy!(pointer(buf), pos, len) + env = transcode(String, buf) + m = match(r"^(=?[^=]+)=(.*)$"s, env) + if m === nothing + error("malformed environment entry: $env") + end + return (Pair{String,String}(m.captures[1], m.captures[2]), (pos+len*2, blk)) end - return (Pair{String,String}(m.captures[1], m.captures[2]), (pos+len*2, blk)) -end - else # !windows -start(::EnvHash) = 0 -done(::EnvHash, i) = (ccall(:jl_environ, Any, (Int32,), i) === nothing) + start(::EnvHash) = 0 + done(::EnvHash, i) = (ccall(:jl_environ, Any, (Int32,), i) === nothing) -function next(::EnvHash, i) - env = ccall(:jl_environ, Any, (Int32,), i) - if env === nothing - throw(BoundsError()) - end - env = env::String - m = match(r"^(.*?)=(.*)$"s, env) - if m === nothing - error("malformed environment entry: $env") + function next(::EnvHash, i) + env = ccall(:jl_environ, Any, (Int32,), i) + if env === nothing + throw(BoundsError()) + end + env = env::String + m = match(r"^(.*?)=(.*)$"s, env) + if m === nothing + error("malformed environment entry: $env") + end + return (Pair{String,String}(m.captures[1], m.captures[2]), i+1) end - return (Pair{String,String}(m.captures[1], m.captures[2]), i+1) -end - end # os-test #TODO: Make these more efficent