aboutsummaryrefslogtreecommitdiff
path: root/libc/libc-test
diff options
context:
space:
mode:
authorDaniel Mueller <deso@posteo.net>2019-11-01 07:42:33 -0700
committerDaniel Mueller <deso@posteo.net>2019-11-01 07:42:33 -0700
commit49be10a8179165d24bbb8eb3490c4ca6f94b42c4 (patch)
tree642978648e57ba8b162a7378e8754df85ca83b37 /libc/libc-test
parent9f3991a74fa5124e298582afa60b229dd005be40 (diff)
downloadnitrocli-49be10a8179165d24bbb8eb3490c4ca6f94b42c4.tar.gz
nitrocli-49be10a8179165d24bbb8eb3490c4ca6f94b42c4.tar.bz2
Update libc crate to 0.2.66
This change updates the libc crate to version 0.2.66. Import subrepo libc/:libc at 4f11029a68040c90acf771976b019c1ef273a8cd
Diffstat (limited to 'libc/libc-test')
-rw-r--r--libc/libc-test/build.rs342
1 files changed, 260 insertions, 82 deletions
diff --git a/libc/libc-test/build.rs b/libc/libc-test/build.rs
index 146beb0..977ff51 100644
--- a/libc/libc-test/build.rs
+++ b/libc/libc-test/build.rs
@@ -27,10 +27,28 @@ fn do_ctest() {
t if t.contains("solaris") => return test_solaris(t),
t if t.contains("wasi") => return test_wasi(t),
t if t.contains("windows") => return test_windows(t),
+ t if t.contains("vxworks") => return test_vxworks(t),
t => panic!("unknown target {}", t),
}
}
+fn ctest_cfg() -> ctest::TestGenerator {
+ let mut cfg = ctest::TestGenerator::new();
+ let libc_cfgs = [
+ "libc_priv_mod_use",
+ "libc_union",
+ "libc_const_size_of",
+ "libc_align",
+ "libc_core_cvoid",
+ "libc_packedN",
+ "libc_thread_local",
+ ];
+ for f in &libc_cfgs {
+ cfg.cfg(f, None);
+ }
+ cfg
+}
+
fn main() {
do_cc();
do_ctest();
@@ -59,8 +77,9 @@ macro_rules! headers {
fn test_apple(target: &str) {
assert!(target.contains("apple"));
let x86_64 = target.contains("x86_64");
+ let i686 = target.contains("i686");
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
cfg.define("__APPLE_USE_RFC_3542", None);
@@ -226,18 +245,17 @@ fn test_apple(target: &str) {
});
cfg.skip_roundtrip(move |s| match s {
- // FIXME: TODO
- "utsname" | "statfs" | "dirent" | "utmpx" => true,
+ // FIXME: this type has the wrong ABI
+ "max_align_t" if i686 => true,
_ => false,
});
-
cfg.generate("../src/lib.rs", "main.rs");
}
fn test_openbsd(target: &str) {
assert!(target.contains("openbsd"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
headers! { cfg:
@@ -264,6 +282,7 @@ fn test_openbsd(target: &str) {
"netinet/ip.h",
"netinet/tcp.h",
"netinet/udp.h",
+ "net/bpf.h",
"resolv.h",
"pthread.h",
"dlfcn.h",
@@ -303,6 +322,7 @@ fn test_openbsd(target: &str) {
"ufs/ufs/quota.h",
"pthread_np.h",
"sys/syscall.h",
+ "sys/shm.h",
}
cfg.skip_struct(move |ty| {
@@ -369,11 +389,6 @@ fn test_openbsd(target: &str) {
(struct_ == "siginfo_t" && field == "si_addr")
});
- cfg.skip_roundtrip(move |s| match s {
- "dirent" | "utsname" | "utmp" => true,
- _ => false,
- });
-
cfg.generate("../src/lib.rs", "main.rs");
}
@@ -381,7 +396,7 @@ fn test_windows(target: &str) {
assert!(target.contains("windows"));
let gnu = target.contains("gnu");
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.define("_WIN32_WINNT", Some("0x8000"));
headers! { cfg:
@@ -478,18 +493,13 @@ fn test_windows(target: &str) {
}
});
- cfg.skip_roundtrip(move |s| match s {
- "dirent" | "statfs" | "utsname" | "utmpx" => true,
- _ => false,
- });
-
cfg.generate("../src/lib.rs", "main.rs");
}
fn test_redox(target: &str) {
assert!(target.contains("redox"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
headers! {
@@ -555,7 +565,7 @@ fn test_redox(target: &str) {
fn test_cloudabi(target: &str) {
assert!(target.contains("cloudabi"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
headers! {
@@ -626,7 +636,7 @@ fn test_cloudabi(target: &str) {
fn test_solaris(target: &str) {
assert!(target.contains("solaris"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
cfg.define("_XOPEN_SOURCE", Some("700"));
@@ -737,7 +747,7 @@ fn test_solaris(target: &str) {
fn test_netbsd(target: &str) {
assert!(target.contains("netbsd"));
let rumprun = target.contains("rumprun");
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
cfg.define("_NETBSD_SOURCE", Some("1"));
@@ -809,6 +819,7 @@ fn test_netbsd(target: &str) {
"netinet/dccp.h",
"sys/event.h",
"sys/quota.h",
+ "sys/shm.h",
}
cfg.type_name(move |ty, is_struct, is_union| {
@@ -937,7 +948,7 @@ fn test_netbsd(target: &str) {
fn test_dragonflybsd(target: &str) {
assert!(target.contains("dragonfly"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.flag("-Wno-deprecated-declarations");
headers! {
@@ -1142,7 +1153,7 @@ fn test_dragonflybsd(target: &str) {
fn test_wasi(target: &str) {
assert!(target.contains("wasi"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.define("_GNU_SOURCE", None);
headers! { cfg:
@@ -1219,7 +1230,7 @@ fn test_android(target: &str) {
};
let x86 = target.contains("i686") || target.contains("x86_64");
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.define("_GNU_SOURCE", None);
headers! { cfg:
@@ -1244,6 +1255,8 @@ fn test_android(target: &str) {
"linux/memfd.h",
"linux/module.h",
"linux/net_tstamp.h",
+ "linux/netfilter/nfnetlink.h",
+ "linux/netfilter/nfnetlink_log.h",
"linux/netfilter/nf_tables.h",
"linux/netfilter_ipv4.h",
"linux/netfilter_ipv6.h",
@@ -1437,13 +1450,6 @@ fn test_android(target: &str) {
field == "ssi_arch"))
});
- let bit64 = target.contains("64");
- cfg.skip_roundtrip(move |s| match s {
- "utsname" | "dirent" | "dirent64" => true,
- "utmp" if bit64 => true,
- _ => false,
- });
-
cfg.generate("../src/lib.rs", "main.rs");
test_linux_like_apis(target);
@@ -1451,11 +1457,12 @@ fn test_android(target: &str) {
fn test_freebsd(target: &str) {
assert!(target.contains("freebsd"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
let freebsd_ver = which_freebsd();
match freebsd_ver {
+ Some(10) => cfg.cfg("freebsd10", None),
Some(11) => cfg.cfg("freebsd11", None),
Some(12) => cfg.cfg("freebsd12", None),
Some(13) => cfg.cfg("freebsd13", None),
@@ -1465,7 +1472,10 @@ fn test_freebsd(target: &str) {
// Required for `getline`:
cfg.define("_WITH_GETLINE", None);
// Required for making freebsd11_stat available in the headers
- cfg.define("_WANT_FREEBSD11_STAT", None);
+ match freebsd_ver {
+ Some(10) => &mut cfg,
+ _ => cfg.define("_WANT_FREEBSD11_STAT", None),
+ };
headers! { cfg:
"aio.h",
@@ -1593,6 +1603,34 @@ fn test_freebsd(target: &str) {
true
}
+ // These constants were introduced in FreeBSD 11:
+ "SF_USER_READAHEAD"
+ | "SF_NOCACHE"
+ | "RLIMIT_KQUEUES"
+ | "RLIMIT_UMTXP"
+ | "EVFILT_PROCDESC"
+ | "EVFILT_SENDFILE"
+ | "EVFILT_EMPTY"
+ | "SO_REUSEPORT_LB"
+ | "TCP_CCALGOOPT"
+ | "TCP_PCAP_OUT"
+ | "TCP_PCAP_IN"
+ | "IP_BINDMULTI"
+ | "IP_ORIGDSTADDR"
+ | "IP_RECVORIGDSTADDR"
+ | "IPV6_ORIGDSTADDR"
+ | "IPV6_RECVORIGDSTADDR"
+ | "PD_CLOEXEC"
+ | "PD_ALLOWED_AT_FORK"
+ | "IP_RSS_LISTEN_BUCKET"
+ if Some(10) == freebsd_ver =>
+ {
+ true
+ }
+
+ // FIXME: This constant has a different value in FreeBSD 10:
+ "RLIM_NLIMITS" if Some(10) == freebsd_ver => true,
+
// FIXME: There are deprecated - remove in a couple of releases.
// These constants were removed in FreeBSD 11 (svn r273250) but will
// still be accepted and ignored at runtime.
@@ -1608,12 +1646,35 @@ fn test_freebsd(target: &str) {
}
});
+ cfg.skip_struct(move |ty| {
+ match ty {
+ // `mmsghdr` is not available in FreeBSD 10
+ "mmsghdr" if Some(10) == freebsd_ver => true,
+
+ // `max_align_t` is not available in FreeBSD 10
+ "max_align_t" if Some(10) == freebsd_ver => true,
+
+ _ => false,
+ }
+ });
+
cfg.skip_fn(move |name| {
// skip those that are manually verified
match name {
// FIXME: https://github.com/rust-lang/libc/issues/1272
"execv" | "execve" | "execvp" | "execvpe" | "fexecve" => true,
+ // These functions were added in FreeBSD 11:
+ "fdatasync" | "mq_getfd_np" | "sendmmsg" | "recvmmsg"
+ if Some(10) == freebsd_ver =>
+ {
+ true
+ }
+
+ // This function changed its return type from `int` in FreeBSD10 to
+ // `ssize_t` in FreeBSD11:
+ "aio_waitcomplete" if Some(10) == freebsd_ver => true,
+
// The `uname` function in the `utsname.h` FreeBSD header is a C
// inline function (has no symbol) that calls the `__xuname` symbol.
// Therefore the function pointer comparison does not make sense for it.
@@ -1629,6 +1690,14 @@ fn test_freebsd(target: &str) {
}
});
+ cfg.skip_signededness(move |c| {
+ match c {
+ // FIXME: has a different sign in FreeBSD10
+ "blksize_t" if Some(10) == freebsd_ver => true,
+ _ => false,
+ }
+ });
+
cfg.volatile_item(|i| {
use ctest::VolatileItemKind::*;
match i {
@@ -1642,14 +1711,17 @@ fn test_freebsd(target: &str) {
});
cfg.skip_field(move |struct_, field| {
- // FIXME: `sa_sigaction` has type `sighandler_t` but that type is
- // incorrect, see: https://github.com/rust-lang/libc/issues/1359
- (struct_ == "sigaction" && field == "sa_sigaction")
- });
+ match (struct_, field) {
+ // FIXME: `sa_sigaction` has type `sighandler_t` but that type is
+ // incorrect, see: https://github.com/rust-lang/libc/issues/1359
+ ("sigaction", "sa_sigaction") => true,
- cfg.skip_roundtrip(move |s| match s {
- "dirent" | "statfs" | "utsname" | "utmpx" => true,
- _ => false,
+ // FIXME: in FreeBSD10 this field has type `char*` instead of
+ // `void*`:
+ ("stack_t", "ss_sp") if Some(10) == freebsd_ver => true,
+
+ _ => false,
+ }
});
cfg.generate("../src/lib.rs", "main.rs");
@@ -1658,7 +1730,7 @@ fn test_freebsd(target: &str) {
fn test_emscripten(target: &str) {
assert!(target.contains("emscripten"));
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.define("_GNU_SOURCE", None); // FIXME: ??
headers! { cfg:
@@ -1856,16 +1928,116 @@ fn test_emscripten(target: &str) {
field == "ssi_arch"))
});
+ // FIXME: test linux like
+ cfg.generate("../src/lib.rs", "main.rs");
+}
+
+fn test_vxworks(target: &str) {
+ assert!(target.contains("vxworks"));
+
+ let mut cfg = ctest::TestGenerator::new();
+ headers! { cfg:
+ "vxWorks.h",
+ "yvals.h",
+ "nfs/nfsCommon.h",
+ "rtpLibCommon.h",
+ "randomNumGen.h",
+ "taskLib.h",
+ "sysLib.h",
+ "ioLib.h",
+ "inetLib.h",
+ "socket.h",
+ "errnoLib.h",
+ "ctype.h",
+ "dirent.h",
+ "dlfcn.h",
+ "elf.h",
+ "fcntl.h",
+ "grp.h",
+ "sys/poll.h",
+ "ifaddrs.h",
+ "langinfo.h",
+ "limits.h",
+ "link.h",
+ "locale.h",
+ "sys/stat.h",
+ "netdb.h",
+ "pthread.h",
+ "pwd.h",
+ "sched.h",
+ "semaphore.h",
+ "signal.h",
+ "stddef.h",
+ "stdint.h",
+ "stdio.h",
+ "stdlib.h",
+ "string.h",
+ "sys/file.h",
+ "sys/ioctl.h",
+ "sys/socket.h",
+ "sys/time.h",
+ "sys/times.h",
+ "sys/types.h",
+ "sys/uio.h",
+ "sys/un.h",
+ "sys/utsname.h",
+ "sys/wait.h",
+ "netinet/tcp.h",
+ "syslog.h",
+ "termios.h",
+ "time.h",
+ "ucontext.h",
+ "unistd.h",
+ "utime.h",
+ "wchar.h",
+ "errno.h",
+ "sys/mman.h",
+ "pathLib.h",
+ }
+ /* Fix me */
+ cfg.skip_const(move |name| match name {
+ // sighandler_t weirdness
+ "SIG_DFL" | "SIG_ERR" | "SIG_IGN"
+ // This is not defined in vxWorks
+ | "RTLD_DEFAULT" => true,
+ _ => false,
+ });
+ /* Fix me */
+ cfg.skip_type(move |ty| match ty {
+ "stat64" | "sighandler_t" | "off64_t" => true,
+ _ => false,
+ });
+
+ cfg.skip_field_type(move |struct_, field| match (struct_, field) {
+ ("siginfo_t", "si_value")
+ | ("stat", "st_size")
+ | ("sigaction", "sa_u") => true,
+ _ => false,
+ });
+
cfg.skip_roundtrip(move |s| match s {
- "pthread_mutexattr_t"
- | "utsname"
- | "dirent"
- | "dirent64"
- | "sysinfo" => true,
_ => false,
});
- // FIXME: test linux like
+ cfg.type_name(move |ty, is_struct, is_union| match ty {
+ "DIR" | "FILE" | "Dl_info" | "RTP_DESC" => ty.to_string(),
+ t if is_union => format!("union {}", t),
+ t if t.ends_with("_t") => t.to_string(),
+ t if is_struct => format!("struct {}", t),
+ t => t.to_string(),
+ });
+
+ /* Fix me */
+ cfg.skip_fn(move |name| match name {
+ /* sigval */
+ "sigqueue" | "_sigqueue"
+ /* sighandler_t*/
+ | "signal"
+ /* not used in static linking by default */
+ | "dlerror" => true,
+ _ => false,
+ });
+
cfg.generate("../src/lib.rs", "main.rs");
}
@@ -1888,17 +2060,19 @@ fn test_linux(target: &str) {
}
let arm = target.contains("arm");
- let x86_64 = target.contains("x86_64");
- let x86_32 = target.contains("i686");
- let x32 = target.contains("x32");
+ let i686 = target.contains("i686");
let mips = target.contains("mips");
let mips32 = mips && !target.contains("64");
let mips64 = mips && target.contains("64");
- let mips32_musl = mips32 && musl;
- let sparc64 = target.contains("sparc64");
+ let ppc64 = target.contains("powerpc64");
let s390x = target.contains("s390x");
+ let sparc64 = target.contains("sparc64");
+ let x32 = target.contains("x32");
+ let x86_32 = target.contains("i686");
+ let x86_64 = target.contains("x86_64");
+ let aarch64_musl = target.contains("aarch64") && musl;
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.define("_GNU_SOURCE", None);
// This macro re-deifnes fscanf,scanf,sscanf to link to the symbols that are
// deprecated since glibc >= 2.29. This allows Rust binaries to link against
@@ -1960,8 +2134,7 @@ fn test_linux(target: &str) {
"sys/prctl.h",
"sys/ptrace.h",
"sys/quota.h",
- // FIXME: the mips-musl CI build jobs use ancient musl 1.0.15:
- [!mips32_musl]: "sys/random.h",
+ "sys/random.h",
"sys/reboot.h",
"sys/resource.h",
"sys/sem.h",
@@ -2015,8 +2188,7 @@ fn test_linux(target: &str) {
"linux/fs.h",
"linux/futex.h",
"linux/genetlink.h",
- // FIXME: musl version 1.0.15 used by mips build jobs is ancient
- [!mips32_musl]: "linux/if.h",
+ "linux/if.h",
"linux/if_addr.h",
"linux/if_alg.h",
"linux/if_ether.h",
@@ -2026,6 +2198,8 @@ fn test_linux(target: &str) {
"linux/memfd.h",
"linux/module.h",
"linux/net_tstamp.h",
+ "linux/netfilter/nfnetlink.h",
+ "linux/netfilter/nfnetlink_log.h",
"linux/netfilter/nf_tables.h",
"linux/netfilter_ipv4.h",
"linux/netfilter_ipv6.h",
@@ -2060,6 +2234,9 @@ fn test_linux(target: &str) {
t if t.ends_with("_t") => t.to_string(),
+ // In MUSL `flock64` is a typedef to `flock`.
+ "flock64" if musl => format!("struct {}", ty),
+
// put `struct` in front of all structs:.
t if is_struct => format!("struct {}", t),
@@ -2131,9 +2308,6 @@ fn test_linux(target: &str) {
// structs.
"termios2" => true,
- // FIXME: musl version using by mips build jobs 1.0.15 is ancient:
- "ifmap" | "ifreq" | "ifconf" if mips32_musl => true,
-
// FIXME: remove once Ubuntu 20.04 LTS is released, somewhere in 2020.
// ucontext_t added a new field as of glibc 2.28; our struct definition is
// conservative and omits the field, but that means the size doesn't match for newer
@@ -2177,7 +2351,7 @@ fn test_linux(target: &str) {
// Require Linux kernel 5.1:
"F_SEAL_FUTURE_WRITE" => true,
- // The musl version 1.0.22 used in CI does not
+ // The musl version 1.1.24 used in CI does not
// contain these glibc constants yet:
| "RLIMIT_RTTIME" // should be in `resource.h`
| "TCP_COOKIE_TRANSACTIONS" // should be in the `netinet/tcp.h` header
@@ -2199,10 +2373,6 @@ fn test_linux(target: &str) {
// - these constants are used by the glibc implementation.
n if musl && n.contains("__SIZEOF_PTHREAD") => true,
- // FIXME: musl version 1.0.15 used by mips build jobs is ancient
- t if mips32_musl && t.starts_with("IFF") => true,
- "MFD_HUGETLB" | "AF_XDP" | "PF_XDP" if mips32_musl => true,
-
_ => false,
}
});
@@ -2286,27 +2456,32 @@ fn test_linux(target: &str) {
field == "_pad2" ||
field == "ssi_syscall" ||
field == "ssi_call_addr" ||
- field == "ssi_arch"))
+ field == "ssi_arch")) ||
+ // FIXME: After musl 1.1.24, it have only one field `sched_priority`,
+ // while other fields become reserved.
+ (struct_ == "sched_param" && [
+ "sched_ss_low_priority",
+ "sched_ss_repl_period",
+ "sched_ss_init_budget",
+ "sched_ss_max_repl",
+ ].contains(&field) && musl) ||
+ // FIXME: After musl 1.1.24, the type becomes `int` instead of `unsigned short`.
+ (struct_ == "ipc_perm" && field == "__seq" && aarch64_musl)
});
cfg.skip_roundtrip(move |s| match s {
- // FIXME: TODO
- "_libc_fpstate" | "user_fpregs_struct" if x86_64 => true,
- "utsname"
- | "statx"
- | "dirent"
- | "dirent64"
- | "utmpx"
- | "user"
- | "user_fpxregs_struct" => true,
- "sysinfo" if musl => true,
- "ucontext_t" if x86_64 && musl => true,
+ // FIXME:
+ "utsname" if mips32 || mips64 => true,
+ // FIXME:
+ "mcontext_t" if s390x => true,
+
"sockaddr_un" | "sembuf" | "ff_constant_effect"
if mips32 && (gnu || musl) =>
{
true
}
"ipv6_mreq"
+ | "ip_mreq_source"
| "sockaddr_in6"
| "sockaddr_ll"
| "in_pktinfo"
@@ -2327,7 +2502,9 @@ fn test_linux(target: &str) {
{
true
}
- "mcontext_t" if s390x => true,
+
+ // FIXME: the call ABI of max_align_t is incorrect on these platforms:
+ "max_align_t" if i686 || mips64 || ppc64 => true,
_ => false,
});
@@ -2348,7 +2525,7 @@ fn test_linux_like_apis(target: &str) {
if linux || android || emscripten {
// test strerror_r from the `string.h` header
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.skip_type(|_| true).skip_static(|_| true);
headers! { cfg: "string.h" }
@@ -2364,7 +2541,7 @@ fn test_linux_like_apis(target: &str) {
if linux || android || emscripten {
// test fcntl - see:
// http://man7.org/linux/man-pages/man2/fcntl.2.html
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
if musl {
cfg.header("fcntl.h");
@@ -2394,7 +2571,7 @@ fn test_linux_like_apis(target: &str) {
if linux || android {
// test termios
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.header("asm/termbits.h");
cfg.skip_type(|_| true)
.skip_static(|_| true)
@@ -2411,7 +2588,7 @@ fn test_linux_like_apis(target: &str) {
if linux || android {
// test IPV6_ constants:
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
headers! {
cfg:
"linux/in6.h"
@@ -2442,7 +2619,7 @@ fn test_linux_like_apis(target: &str) {
// These types have a field called `p_type`, but including
// "resolve.h" defines a `p_type` macro that expands to `__p_type`
// making the tests for these fails when both are included.
- let mut cfg = ctest::TestGenerator::new();
+ let mut cfg = ctest_cfg();
cfg.header("elf.h");
cfg.skip_fn(|_| true)
.skip_static(|_| true)
@@ -2472,6 +2649,7 @@ fn which_freebsd() -> Option<i32> {
let stdout = String::from_utf8(output.stdout).ok()?;
match &stdout {
+ s if s.starts_with("10") => Some(10),
s if s.starts_with("11") => Some(11),
s if s.starts_with("12") => Some(12),
s if s.starts_with("13") => Some(13),