mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-26 22:38:30 +00:00
Write more redbean unit tests
- Fix DescribeSigset() - Introduce new unix.rmrf() API - Fix redbean sigaction() doc example code - Fix unix.sigaction() w/ more than two args - Improve redbean re module API (non-breaking) - Enhance Lua with Python string multiplication - Make third parameter of unix.socket() default to 0
This commit is contained in:
parent
c5b9902ac9
commit
1c83670229
20 changed files with 738 additions and 204 deletions
38
test/libc/intrin/describesigset_test.c
Normal file
38
test/libc/intrin/describesigset_test.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/struct/sigset.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/testlib/testlib.h"
|
||||
|
||||
TEST(DescribeSigset, present) {
|
||||
sigset_t ss;
|
||||
sigemptyset(&ss);
|
||||
sigaddset(&ss, SIGINT);
|
||||
sigaddset(&ss, SIGUSR1);
|
||||
EXPECT_STREQ("{INT,USR1}", DescribeSigset(0, &ss));
|
||||
}
|
||||
|
||||
TEST(DescribeSigset, absent) {
|
||||
sigset_t ss;
|
||||
sigfillset(&ss);
|
||||
sigdelset(&ss, SIGINT);
|
||||
sigdelset(&ss, SIGUSR1);
|
||||
EXPECT_STREQ("~{INT,USR1}", DescribeSigset(0, &ss));
|
||||
}
|
28
test/tool/net/argon2_test.lua
Normal file
28
test/tool/net/argon2_test.lua
Normal file
|
@ -0,0 +1,28 @@
|
|||
-- Copyright 2022 Justine Alexandra Roberts Tunney
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for
|
||||
-- any purpose with or without fee is hereby granted, provided that the
|
||||
-- above copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
-- DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
-- PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
-- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
assert(assert(argon2.hash_encoded("password", "somesalt", {
|
||||
variant = argon2.variants.argon2_i,
|
||||
m_cost = 65536,
|
||||
hash_len = 24,
|
||||
parallelism = 4,
|
||||
t_cost = 2,
|
||||
})) ==
|
||||
"$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG")
|
||||
|
||||
assert(argon2.verify(
|
||||
"$argon2i$v=19$m=65536,t=2," ..
|
||||
"p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG",
|
||||
"password"))
|
|
@ -17,6 +17,9 @@ x = Rdtsc()
|
|||
y = Rdtsc()
|
||||
assert(y > x)
|
||||
|
||||
assert(Rdrand() ~= Rdrand())
|
||||
assert(Rdseed() ~= Rdseed())
|
||||
|
||||
assert(Bsr(1) == 0)
|
||||
assert(Bsr(2) == 1)
|
||||
assert(Bsr(3) == 1)
|
||||
|
@ -29,22 +32,54 @@ assert(Bsf(3) == 0)
|
|||
assert(Bsf(4) == 2)
|
||||
assert(Bsf(0x80000001) == 0)
|
||||
|
||||
assert(Popcnt(0) == 0)
|
||||
assert(Popcnt(1) == 1)
|
||||
assert(Popcnt(2) == 1)
|
||||
assert(Popcnt(3) == 2)
|
||||
assert(Popcnt(0b0111101001101001) == 9)
|
||||
|
||||
assert(Lemur64() == 0x1940efe9d47ae889)
|
||||
assert(Lemur64() == 0xd4b3103f567f9974)
|
||||
|
||||
assert(hex(0x1940efe9d47ae889) == "0x1940efe9d47ae889")
|
||||
assert(oct(0x1940efe9d47ae889) == "0145007376472436564211")
|
||||
assert(bin(0x1940efe9d47ae889) == "0b0001100101000000111011111110100111010100011110101110100010001001")
|
||||
|
||||
assert(EscapeHtml("?hello&there<>") == "?hello&there<>")
|
||||
assert(EscapeParam("?hello&there<>") == "%3Fhello%26there%3C%3E")
|
||||
|
||||
assert(DecodeLatin1("hello\xff\xc0") == "helloÿÀ")
|
||||
assert(EncodeLatin1("helloÿÀ") == "hello\xff\xc0")
|
||||
|
||||
assert(EncodeLua(nil) == "nil")
|
||||
assert(EncodeLua(0) == "0")
|
||||
assert(EncodeLua(3.14) == "3.14")
|
||||
assert(EncodeLua({1, 2}) == "{1, 2}")
|
||||
x = {1, 2}
|
||||
x[3] = x
|
||||
assert(string.match(EncodeLua(x), "{1, 2, \"cyclic@0x%x+\"}"))
|
||||
|
||||
-- TODO(jart): EncodeLua() should sort tables
|
||||
-- x = {}
|
||||
-- x.c = 'c'
|
||||
-- x.a = 'a'
|
||||
-- x.b = 'b'
|
||||
-- assert(EncodeLua(x) == '{a="a", b="b", c="c"}')
|
||||
|
||||
assert(EncodeJson(nil) == "null")
|
||||
assert(EncodeJson(0) == "0")
|
||||
assert(EncodeJson(3.14) == "3.14")
|
||||
assert(EncodeJson({1, 2}) == "[1,2]")
|
||||
|
||||
assert(hex(0x1940efe9d47ae889) == "0x1940efe9d47ae889")
|
||||
assert(oct(0x1940efe9d47ae889) == "0145007376472436564211")
|
||||
assert(bin(0x1940efe9d47ae889) == "0b0001100101000000111011111110100111010100011110101110100010001001")
|
||||
url = ParseUrl("https://jart:pass@redbean.dev/2.0.html?x&y=z#frag")
|
||||
assert(url.scheme == "https")
|
||||
assert(url.user == "jart")
|
||||
assert(url.pass == "pass")
|
||||
assert(url.host == "redbean.dev")
|
||||
assert(not url.port)
|
||||
assert(url.path == "/2.0.html")
|
||||
assert(EncodeLua(url.params) == '{{"x"}, {"y", "z"}}')
|
||||
assert(url.fragment == "frag")
|
||||
|
||||
assert(DecodeBase64("abcdefgABCDE") == "\x69\xb7\x1d\x79\xf8\x00\x04\x20\xc4")
|
||||
assert(EncodeBase64("\x69\xb7\x1d\x79\xf8\x00\x04\x20\xc4") == "abcdefgABCDE")
|
||||
|
@ -82,6 +117,7 @@ assert(IndentLines("hi\nthere\n") == " hi\n there\n")
|
|||
assert(IndentLines("hi\nthere\n", 2) == " hi\n there\n")
|
||||
|
||||
assert(ParseHttpDateTime("Fri, 08 Jul 2022 16:17:43 GMT") == 1657297063)
|
||||
assert(FormatHttpDateTime(1657297063) == "Fri, 08 Jul 2022 16:17:43 GMT")
|
||||
|
||||
assert(VisualizeControlCodes("hello\x00") == "hello␀")
|
||||
|
||||
|
@ -97,8 +133,8 @@ assert(Sha256("hello") == "\x2c\xf2\x4d\xba\x5f\xb0\xa3\x0e\x26\xe8\x3b\x2a\xc5\
|
|||
assert(Sha384("hello") == "\x59\xe1\x74\x87\x77\x44\x8c\x69\xde\x6b\x80\x0d\x7a\x33\xbb\xfb\x9f\xf1\xb4\x63\xe4\x43\x54\xc3\x55\x3b\xcd\xb9\xc6\x66\xfa\x90\x12\x5a\x3c\x79\xf9\x03\x97\xbd\xf5\xf6\xa1\x3d\xe8\x28\x68\x4f")
|
||||
assert(Sha512("hello") == "\x9b\x71\xd2\x24\xbd\x62\xf3\x78\x5d\x96\xd4\x6a\xd3\xea\x3d\x73\x31\x9b\xfb\xc2\x89\x0c\xaa\xda\xe2\xdf\xf7\x25\x19\x67\x3c\xa7\x23\x23\xc3\xd9\x9b\xa5\xc1\x1d\x7c\x7a\xcc\x6e\x14\xb8\xc5\xda\x0c\x46\x63\x47\x5c\x2e\x5c\x3a\xde\xf4\x6f\x73\xbc\xde\xc0\x43")
|
||||
|
||||
assert(Deflate("hello") == "\xcbH\xcd\xc9\xc9\x07\x00")
|
||||
assert(Inflate("\xcbH\xcd\xc9\xc9\x07\x00", 5) == "hello")
|
||||
assert(assert(Deflate("hello")) == "\xcbH\xcd\xc9\xc9\x07\x00")
|
||||
assert(assert(Inflate("\xcbH\xcd\xc9\xc9\x07\x00", 5)) == "hello")
|
||||
|
||||
-- deprecated compression api we wish to forget as quickly as possible
|
||||
assert(Uncompress(Compress("hello")) == "hello")
|
||||
|
|
40
test/tool/net/lre_test.lua
Normal file
40
test/tool/net/lre_test.lua
Normal file
|
@ -0,0 +1,40 @@
|
|||
-- Copyright 2022 Justine Alexandra Roberts Tunney
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for
|
||||
-- any purpose with or without fee is hereby granted, provided that the
|
||||
-- above copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
-- DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
-- PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
-- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
m,a,b,c,d = assert(re.search([[^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$]], "127.0.0.1"))
|
||||
assert(m == "127.0.0.1")
|
||||
assert(a == "127")
|
||||
assert(b == "0")
|
||||
assert(c == "0")
|
||||
assert(d == "1")
|
||||
|
||||
p = assert(re.compile[[^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$]])
|
||||
m,a,b,c,d = assert(p:search("127.0.0.1"))
|
||||
assert(m == "127.0.0.1")
|
||||
assert(a == "127")
|
||||
assert(b == "0")
|
||||
assert(c == "0")
|
||||
assert(d == "1")
|
||||
|
||||
m,a,b,c,d = assert(re.search([[\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)]], "127.0.0.1", re.BASIC))
|
||||
assert(m == "127.0.0.1")
|
||||
assert(a == "127")
|
||||
assert(b == "0")
|
||||
assert(c == "0")
|
||||
assert(d == "1")
|
||||
|
||||
p,e = re.compile("[{")
|
||||
assert(e:errno() == re.EBRACK)
|
||||
assert(e:doc() == "Missing ']'")
|
21
test/tool/net/lua_test.lua
Normal file
21
test/tool/net/lua_test.lua
Normal file
|
@ -0,0 +1,21 @@
|
|||
-- Copyright 2022 Justine Alexandra Roberts Tunney
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for
|
||||
-- any purpose with or without fee is hereby granted, provided that the
|
||||
-- above copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
-- DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
-- PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
-- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
-- test redbean lua language extensions
|
||||
assert(0b100 == 4)
|
||||
assert(0200 == 128)
|
||||
assert("\e" == "\x1b")
|
||||
assert("hi" * 3 == "hihihi")
|
||||
assert("hello %d" % {123} == "hello 123")
|
|
@ -13,40 +13,147 @@
|
|||
-- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
-- dup()+close()
|
||||
fd = assert(unix.dup(2))
|
||||
assert(unix.close(fd))
|
||||
gotsigusr1 = false
|
||||
tmpdir = "o/tmp/lunix_test.%d" % {unix.getpid()}
|
||||
|
||||
-- dup2()+close()
|
||||
assert(assert(unix.dup(2, 10)) == 10)
|
||||
assert(unix.close(10))
|
||||
|
||||
-- fork()+exit()
|
||||
if assert(unix.fork()) == 0 then
|
||||
unix.exit(42)
|
||||
function OnSigUsr1(sig)
|
||||
gotsigusr1 = true
|
||||
end
|
||||
pid, ws = assert(unix.wait())
|
||||
assert(unix.WIFEXITED(ws))
|
||||
assert(unix.WEXITSTATUS(ws) == 42)
|
||||
|
||||
-- pledge()
|
||||
if GetHostOs() == "LINUX" then
|
||||
function UnixTest()
|
||||
|
||||
-- strsignal
|
||||
assert(unix.strsignal(9) == "SIGKILL")
|
||||
assert(unix.strsignal(unix.SIGKILL) == "SIGKILL")
|
||||
|
||||
-- gmtime
|
||||
year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst,zone = assert(unix.gmtime(1657297063))
|
||||
assert(year == 2022)
|
||||
assert(mon == 7)
|
||||
assert(mday == 8)
|
||||
assert(hour == 16)
|
||||
assert(min == 17)
|
||||
assert(sec == 43)
|
||||
assert(gmtoffsec == 0)
|
||||
assert(wday == 5)
|
||||
assert(yday == 188)
|
||||
assert(dst == 0)
|
||||
assert(zone == "GMT")
|
||||
|
||||
-- dup
|
||||
-- 1. duplicate stderr as lowest available fd
|
||||
-- 1. close the newly assigned file descriptor
|
||||
fd = assert(unix.dup(2))
|
||||
assert(unix.close(fd))
|
||||
|
||||
-- dup2
|
||||
-- 1. duplicate stderr as fd 10
|
||||
-- 1. close the new file descriptor
|
||||
assert(assert(unix.dup(2, 10)) == 10)
|
||||
assert(unix.close(10))
|
||||
|
||||
-- fork
|
||||
-- basic subprocess creation
|
||||
if assert(unix.fork()) == 0 then
|
||||
assert(unix.pledge("stdio"))
|
||||
_, err = unix.socket()
|
||||
assert(err:errno() == unix.EPERM)
|
||||
unix.exit(0)
|
||||
unix.pledge("")
|
||||
unix.exit(42)
|
||||
end
|
||||
pid, ws = assert(unix.wait())
|
||||
assert(unix.WIFEXITED(ws))
|
||||
assert(unix.WEXITSTATUS(ws) == 0)
|
||||
elseif GetHostOs() == "OPENBSD" then
|
||||
if assert(unix.fork()) == 0 then
|
||||
assert(unix.pledge("stdio"))
|
||||
unix.socket()
|
||||
unix.exit(1)
|
||||
assert(unix.WEXITSTATUS(ws) == 42)
|
||||
|
||||
-- pledge
|
||||
-- 1. fork off a process
|
||||
-- 2. sandbox the process
|
||||
-- 3. then violate its security
|
||||
if GetHostOs() == "LINUX" then
|
||||
if assert(unix.fork()) == 0 then
|
||||
assert(unix.pledge("stdio"))
|
||||
_, err = unix.socket()
|
||||
assert(err:errno() == unix.EPERM)
|
||||
unix.exit(0)
|
||||
end
|
||||
pid, ws = assert(unix.wait())
|
||||
assert(unix.WIFEXITED(ws))
|
||||
assert(unix.WEXITSTATUS(ws) == 0)
|
||||
elseif GetHostOs() == "OPENBSD" then
|
||||
if assert(unix.fork()) == 0 then
|
||||
assert(unix.pledge("stdio"))
|
||||
unix.socket()
|
||||
unix.exit(1)
|
||||
end
|
||||
pid, ws = assert(unix.wait())
|
||||
assert(unix.WIFSIGNALED(ws))
|
||||
assert(unix.WTERMSIG(ws) == unix.SIGABRT)
|
||||
end
|
||||
pid, ws = assert(unix.wait())
|
||||
assert(unix.WIFSIGNALED(ws))
|
||||
assert(unix.WTERMSIG(ws) == unix.SIGABRT)
|
||||
|
||||
-- sigaction
|
||||
-- 1. install a signal handler for USR1
|
||||
-- 2. block USR1
|
||||
-- 3. trigger USR1 signal [it gets enqueued]
|
||||
-- 4. pause() w/ atomic unblocking of USR1 [now it gets delivered!]
|
||||
-- 5. restore old signal mask
|
||||
-- 6. restore old sig handler
|
||||
oldhand, oldflags, oldmask = assert(unix.sigaction(unix.SIGUSR1, OnSigUsr1))
|
||||
oldmask = assert(unix.sigprocmask(unix.SIG_BLOCK, unix.Sigset(unix.SIGUSR1)))
|
||||
assert(unix.raise(unix.SIGUSR1))
|
||||
assert(not gotsigusr1)
|
||||
ok, err = unix.sigsuspend(oldmask)
|
||||
assert(not ok)
|
||||
assert(err:errno() == unix.EINTR)
|
||||
assert(gotsigusr1)
|
||||
assert(unix.sigprocmask(unix.SIG_SETMASK, oldmask))
|
||||
assert(unix.sigaction(unix.SIGUSR1, oldhand, oldflags, oldmask))
|
||||
|
||||
-- open
|
||||
-- 1. create file
|
||||
-- 2. fill it up
|
||||
-- 3. inspect it
|
||||
-- 4. mess with it
|
||||
fd = assert(unix.open("%s/foo" % {tmpdir}, unix.O_RDWR | unix.O_CREAT | unix.O_TRUNC, 0600))
|
||||
assert(assert(unix.fstat(fd)):size() == 0)
|
||||
assert(unix.ftruncate(fd, 8192))
|
||||
assert(assert(unix.fstat(fd)):size() == 8192)
|
||||
assert(unix.write(fd, "hello"))
|
||||
assert(unix.lseek(fd, 4096))
|
||||
assert(unix.write(fd, "poke"))
|
||||
assert(unix.lseek(fd, 8192-4))
|
||||
assert(unix.write(fd, "poke"))
|
||||
st = assert(unix.fstat(fd))
|
||||
assert(st:size() == 8192)
|
||||
assert(st:blocks() == 8192/512)
|
||||
assert((st:mode() & 0777) == 0600)
|
||||
assert(st:uid() == unix.getuid())
|
||||
assert(st:gid() == unix.getgid())
|
||||
assert(unix.write(fd, "bear", 4))
|
||||
assert(unix.read(fd, 10, 0) == "hellbear\x00\x00")
|
||||
assert(unix.close(fd))
|
||||
fd = assert(unix.open("%s/foo" % {tmpdir}))
|
||||
assert(unix.lseek(fd, 4))
|
||||
assert(unix.read(fd, 4) == "bear")
|
||||
assert(unix.close(fd))
|
||||
fd = assert(unix.open("%s/foo" % {tmpdir}, unix.O_RDWR))
|
||||
assert(unix.write(fd, "bear"))
|
||||
assert(unix.close(fd))
|
||||
fd = assert(unix.open("%s/foo" % {tmpdir}))
|
||||
assert(unix.read(fd, 8) == "bearbear")
|
||||
assert(unix.close(fd))
|
||||
|
||||
-- getdents
|
||||
for name, kind, ino, off in assert(unix.opendir(tmpdir)) do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function main()
|
||||
assert(unix.makedirs(tmpdir))
|
||||
ok, err = pcall(UnixTest)
|
||||
if ok then
|
||||
assert(unix.rmrf(tmpdir))
|
||||
else
|
||||
print(err)
|
||||
error('UnixTest failed (%s)' % {tmpdir})
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue