mirror of
https://github.com/3proxy/3proxy.git
synced 2026-11-05 01:15:48 +08:00
help: the features of the build listed, and the tests skip what they need
Some checks failed
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Has been cancelled
Some checks failed
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Has been cancelled
3proxy --help (and the usage message of a bad invocation) now lists what the build was made with: TLS and its backend, RADIUS, PCRE, IPv6, UNIX sockets, the built-in HTTP server, transparency, ODBC and the optional proxy services. The test runner reads the list before the first case and the cases state the features they need with t.skip_unless(), so a build without a feature skips its cases instead of failing them; a binary too old to report anything skips the cases with needs and runs the rest.
This commit is contained in:
parent
a75b488aaf
commit
629a978c00
32
src/3proxy.c
32
src/3proxy.c
@ -382,6 +382,21 @@ void cyclestep(void){
|
||||
#define RETURN(x) {res = x; goto CLEARRETURN;}
|
||||
|
||||
|
||||
static void printusage(struct extparam *cconf, int argc, char * argv[]){
|
||||
|
||||
fprintf(stderr, "Usage: %s [conffile]\n", argv[0]);
|
||||
#ifdef _WIN32
|
||||
fprintf(stderr, "\n\t%s --install [conffile]\n\tto install as service\n"
|
||||
"\n\t%s --remove\n\tto remove service\n", argv[0], argv[0]);
|
||||
#else
|
||||
fprintf(stderr, "\n if conffile is missing, configuration is expected from stdin\n");
|
||||
#endif
|
||||
fprintf(stderr, "\n\t%s --help\n\tthis message\n", argv[0]);
|
||||
fprintf(stderr, "available socket options:\n\t%s\n", printopts("\n\t"));
|
||||
fprintf(stderr, "built with:\n\t%s\n", printfeatures("\n\t"));
|
||||
fprintf(stderr, "\n%s %s\n%s\n", cconf->stringtable[2], cconf->stringtable[3], copyright);
|
||||
}
|
||||
|
||||
|
||||
#ifndef _WINCE
|
||||
int main(int argc, char * argv[]) {
|
||||
@ -424,6 +439,10 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
||||
#endif
|
||||
|
||||
conf.stringtable = strings;
|
||||
if(argc == 2 && (!strcmp((char *)argv[1], "--help") || !strcmp((char *)argv[1], "-h"))) {
|
||||
printusage(&conf, argc, argv);
|
||||
return 0;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
#ifndef _WINCE
|
||||
if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--install")) {
|
||||
@ -536,18 +555,9 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
||||
}
|
||||
if(argc > 2 || !(fp)) {
|
||||
|
||||
fprintf(stderr, "Usage: %s [conffile]\n", argv[0]);
|
||||
#ifdef _WIN32
|
||||
fprintf(stderr, "\n\t%s --install [conffile]\n\tto install as service\n"
|
||||
"\n\t%s --remove\n\tto remove service\n", argv[0], argv[0]);
|
||||
#else
|
||||
fprintf(stderr, "\n if conffile is missing, configuration is expected from stdin\n");
|
||||
#endif
|
||||
fprintf(stderr, "available socket options:\n\t%s\n", printopts("\n\t"));
|
||||
fprintf(stderr, "\n%s %s\n%s\n", conf.stringtable[2], conf.stringtable[3], copyright);
|
||||
|
||||
printusage(&conf, argc, argv);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
/* before readconfig() starts the services: a client gone while it is
|
||||
|
||||
@ -512,6 +512,7 @@ extern struct socketoptions {
|
||||
int getopts(const char *s, struct sockoptset *opts);
|
||||
void setopts(SOCKET s, const struct sockoptset *opts);
|
||||
char * printopts(char *sep);
|
||||
char * printfeatures(char *sep);
|
||||
|
||||
#ifdef _WINCE
|
||||
char * CEToUnicode (const char *str);
|
||||
|
||||
@ -226,6 +226,69 @@ char * printopts(char *sep){
|
||||
}
|
||||
|
||||
|
||||
char featsbuf[1024];
|
||||
|
||||
/* The features of this build, by the names the manual and the test suite
|
||||
know them, so a user can see what a binary was made with. */
|
||||
char * printfeatures(char *sep){
|
||||
int pos=0;
|
||||
|
||||
#define ADDFEATURE(x) pos += sprintf(featsbuf+pos,"%s%s",pos?sep:"",x)
|
||||
#ifdef WITH_SSL
|
||||
ADDFEATURE("ssl");
|
||||
#ifdef WITH_WOLFSSL
|
||||
ADDFEATURE("wolfssl");
|
||||
#else
|
||||
ADDFEATURE("openssl");
|
||||
#endif
|
||||
#endif
|
||||
#ifndef NORADIUS
|
||||
ADDFEATURE("radius");
|
||||
#endif
|
||||
#ifdef WITH_PCRE
|
||||
ADDFEATURE("pcre");
|
||||
#endif
|
||||
#ifndef NOIPV6
|
||||
ADDFEATURE("ipv6");
|
||||
#endif
|
||||
#ifdef WITH_UN
|
||||
ADDFEATURE("unix");
|
||||
#endif
|
||||
#ifdef WITH_HTTPSRV
|
||||
ADDFEATURE("httpsrv");
|
||||
#endif
|
||||
#ifdef WITH_TRANSPARENT
|
||||
ADDFEATURE("transparent");
|
||||
#endif
|
||||
#ifdef WITH_NETFILTER
|
||||
ADDFEATURE("netfilter");
|
||||
#endif
|
||||
#ifdef WITH_PF
|
||||
ADDFEATURE("pf");
|
||||
#endif
|
||||
#ifdef WITH_ODBC
|
||||
ADDFEATURE("odbc");
|
||||
#endif
|
||||
#ifdef WITH_FTP
|
||||
ADDFEATURE("ftppr");
|
||||
#endif
|
||||
#ifdef WITH_POP3P
|
||||
ADDFEATURE("pop3p");
|
||||
#endif
|
||||
#ifdef WITH_SMTPP
|
||||
ADDFEATURE("smtpp");
|
||||
#endif
|
||||
#ifdef WITH_IMAPP
|
||||
ADDFEATURE("imapp");
|
||||
#endif
|
||||
#ifndef NOPLUGINS
|
||||
ADDFEATURE("plugins");
|
||||
#endif
|
||||
#undef ADDFEATURE
|
||||
return featsbuf;
|
||||
}
|
||||
|
||||
|
||||
/* A comma separated list of options, each NAME or NAME=VALUE; a name alone
|
||||
stands for 1, or for TCP_MAXSEG for the value of maxseg. Options are added
|
||||
to what is already set, so a default is changed only by naming it, and
|
||||
|
||||
@ -13,6 +13,14 @@ skips itself when that is missing or the build has no TLS support. With no
|
||||
`--bin` it looks in `bin/`, then `build/bin/`, then the per-configuration
|
||||
directories a multi-configuration CMake generator uses.
|
||||
|
||||
The binary reports what it was built with in `3proxy --help` (a `built
|
||||
with:` list), and the runner reads that before the first case: a case names
|
||||
the features it needs with `if not t.skip_unless("ssl"): return`, and
|
||||
skips on a build without them instead of failing. A binary too old to
|
||||
report its features skips every case that needs one, and the cases with no
|
||||
feature needs still run; some cases also probe at runtime, as a build can
|
||||
leave a feature compiled in but unusable.
|
||||
|
||||
The proxy under test is also the origin server the tests talk to: the `http`
|
||||
command's `echo` operation reports back how a request arrived - method, path,
|
||||
query, host, and the source port it came from - and `data` generates a body
|
||||
|
||||
@ -4,6 +4,8 @@ import time
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
adm = t.free_port()
|
||||
lim = t.free_port()
|
||||
server = t.start("admin", f"""
|
||||
|
||||
@ -7,7 +7,9 @@ handshake and the service supplies the port.
|
||||
|
||||
|
||||
def run(t):
|
||||
certs = t.certs()
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
certs = t.certs() if t.has("ssl") else None
|
||||
plain = t.free_port()
|
||||
port = t.free_port()
|
||||
secure = t.free_port() if certs else None
|
||||
|
||||
@ -60,6 +60,8 @@ def reply(f):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("ftppr"):
|
||||
return
|
||||
servers = {}
|
||||
for name, (user, password) in LOGINS.items():
|
||||
listener = socket.socket()
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
openport = t.free_port()
|
||||
t.start("httpsrv_auth", f"""
|
||||
|
||||
@ -8,6 +8,8 @@ import os
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
root = os.path.join(t.tmpdir, "web")
|
||||
os.makedirs(os.path.join(root, "picts", "set"), exist_ok=True)
|
||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||
|
||||
@ -10,6 +10,8 @@ import os
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
root = os.path.join(t.tmpdir, "ka")
|
||||
os.makedirs(root, exist_ok=True)
|
||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||
|
||||
@ -4,6 +4,8 @@ import time
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
t.start("httpsrv_ops", f"""
|
||||
log
|
||||
|
||||
@ -6,6 +6,8 @@ most of them away before they ever reached the server.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
t.start("httpsrv_parsing", f"""
|
||||
log
|
||||
|
||||
@ -9,6 +9,8 @@ import os
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
root = os.path.join(t.tmpdir, "pp")
|
||||
os.makedirs(root, exist_ok=True)
|
||||
with open(os.path.join(root, "a.html"), "w") as fp:
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
srv2 = t.free_port()
|
||||
t.start("httpsrv_rules", f"""
|
||||
|
||||
@ -7,6 +7,8 @@ nscache6: nscache holds the IPv4 side and nothing else.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "ipv6"):
|
||||
return
|
||||
if not t.has_ipv6():
|
||||
t.skip("IPv6 (this machine has no IPv6 loopback)")
|
||||
return
|
||||
|
||||
@ -8,6 +8,8 @@ import time
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
# The origin runs in a process of its own, so the limit only applies to
|
||||
# the mapper in front of it.
|
||||
origin = t.free_port()
|
||||
|
||||
@ -30,6 +30,8 @@ def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
good = t.free_port()
|
||||
spare = t.free_port()
|
||||
|
||||
@ -46,6 +46,8 @@ UNHONOURED = (1, 99)
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
prx = t.free_port()
|
||||
sks = t.free_port()
|
||||
|
||||
@ -37,6 +37,8 @@ def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
adm = t.free_port()
|
||||
prx = t.free_port()
|
||||
dummy = t.free_port()
|
||||
|
||||
@ -20,6 +20,8 @@ def _has_pcre(t):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "pcre"):
|
||||
return
|
||||
if not _has_pcre(t):
|
||||
t.skip("PCRE (this build has no PCRE support)")
|
||||
return
|
||||
|
||||
@ -26,6 +26,8 @@ def halfclose_origin(listener):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
# --- tcppm ---------------------------------------------------------
|
||||
origin = t.free_port()
|
||||
mapped = t.free_port()
|
||||
|
||||
@ -6,6 +6,8 @@ from a clean list.
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
other = t.free_port()
|
||||
prx = t.free_port()
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
srv = t.free_port()
|
||||
sks = t.free_port()
|
||||
sauth = t.free_port()
|
||||
|
||||
@ -11,6 +11,8 @@ def _no_tls(t, server):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "ssl"):
|
||||
return
|
||||
certs = t.certs()
|
||||
if not certs:
|
||||
t.skip("TLS (openssl is not available to generate certificates)")
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "ssl"):
|
||||
return
|
||||
certs = t.certs()
|
||||
if not certs:
|
||||
t.skip("tlspr (openssl is not available to generate certificates)")
|
||||
|
||||
@ -39,6 +39,8 @@ def _config(t, name, body):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv"):
|
||||
return
|
||||
# --- the command and its modes ------------------------------------
|
||||
# These need no redirection, so they run wherever the feature is built.
|
||||
out = _config(t, "transparent_probe", "log\ntransparent")
|
||||
|
||||
@ -36,6 +36,8 @@ def unix_get(path, url, timeout=10):
|
||||
|
||||
|
||||
def run(t):
|
||||
if not t.skip_unless("httpsrv", "unix"):
|
||||
return
|
||||
if not hasattr(socket, "AF_UNIX") or sys.platform.startswith("win"):
|
||||
t.skip("Unix domain sockets (not on this platform)")
|
||||
return
|
||||
|
||||
@ -122,10 +122,11 @@ class Failure(Exception):
|
||||
class Tester:
|
||||
"""The API a case runs against: start servers, make requests, assert."""
|
||||
|
||||
def __init__(self, binary, tmpdir, case):
|
||||
def __init__(self, binary, tmpdir, case, features=None):
|
||||
self.binary = binary
|
||||
self.tmpdir = tmpdir
|
||||
self.case = case
|
||||
self.features = features if features is not None else frozenset()
|
||||
self.servers = []
|
||||
self.checks = []
|
||||
self.timeout = 10
|
||||
@ -135,6 +136,25 @@ class Tester:
|
||||
self.logs = []
|
||||
self.udp_servers = []
|
||||
|
||||
# ---- build features ----------------------------------------------
|
||||
|
||||
def has(self, *names):
|
||||
"""Whether the binary reports every named feature."""
|
||||
return all(name in self.features for name in names)
|
||||
|
||||
def skip_unless(self, *names):
|
||||
"""Skip what needs a feature the binary was built without.
|
||||
|
||||
Returns False after recording the skip, so a case says
|
||||
`if not t.skip_unless("ssl"): return` and keeps its checks out of
|
||||
the failed count on a build that cannot pass them.
|
||||
"""
|
||||
missing = [name for name in names if name not in self.features]
|
||||
if not missing:
|
||||
return True
|
||||
self.skip("built without: " + ", ".join(missing))
|
||||
return False
|
||||
|
||||
# ---- servers -----------------------------------------------------
|
||||
|
||||
def has_ipv6(self):
|
||||
|
||||
35
tests/run.py
35
tests/run.py
@ -47,6 +47,37 @@ def load_case(path):
|
||||
return name, module
|
||||
|
||||
|
||||
def built_features(binary):
|
||||
"""The features the binary reports in `3proxy --help`, as a set.
|
||||
|
||||
A binary too old to have the list, or one that cannot be run at all,
|
||||
reports nothing: every case that needs a feature then skips, and the
|
||||
ones that need none still run.
|
||||
"""
|
||||
import subprocess
|
||||
try:
|
||||
done = subprocess.run([binary, "--help"], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, timeout=15)
|
||||
text = done.stdout.decode("utf-8", "replace")
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return frozenset()
|
||||
features = set()
|
||||
collecting = False
|
||||
for line in text.splitlines():
|
||||
stripped = line.strip()
|
||||
if line.startswith("built with:"):
|
||||
collecting = True
|
||||
rest = stripped[len("built with:"):].strip()
|
||||
if rest:
|
||||
features.update(rest.split())
|
||||
continue
|
||||
if collecting:
|
||||
if not line[:1].isspace() or not stripped:
|
||||
break
|
||||
features.update(stripped.split())
|
||||
return frozenset(features)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("pattern", nargs="?", default="",
|
||||
@ -75,7 +106,9 @@ def main():
|
||||
return 2
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="3proxy-tests.")
|
||||
features = built_features(binary)
|
||||
print(f"3proxy tests: {binary}")
|
||||
print(f"built with: {' '.join(sorted(features)) or '(nothing reported)'}")
|
||||
print(f"working in: {tmpdir}\n")
|
||||
|
||||
passed = failed = skipped = 0
|
||||
@ -85,7 +118,7 @@ def main():
|
||||
for path in paths:
|
||||
name, module = load_case(path)
|
||||
print(f" {name}")
|
||||
tester = Tester(binary, tmpdir, name)
|
||||
tester = Tester(binary, tmpdir, name, features)
|
||||
error = None
|
||||
try:
|
||||
module.run(tester)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user