mirror of
https://github.com/3proxy/3proxy.git
synced 2026-11-16 06:05:50 +08:00
Compare commits
4 Commits
371e78691d
...
629a978c00
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
629a978c00 | ||
|
|
a75b488aaf | ||
|
|
e9714fa4df | ||
|
|
9be415b731 |
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
|
||||
|
||||
@ -198,12 +198,20 @@ static int calc_replydigest(char *packet, char *original, const char *secret, in
|
||||
int secretlen;
|
||||
uint8_t calc_digest[AUTH_VECTOR_LEN];
|
||||
uint8_t calc_vector[AUTH_VECTOR_LEN];
|
||||
unsigned int dlen = AUTH_VECTOR_LEN;
|
||||
mdh_ctx *ctx;
|
||||
|
||||
memcpy(calc_vector, packet + 4, AUTH_VECTOR_LEN);
|
||||
memcpy(packet + 4, original, AUTH_VECTOR_LEN);
|
||||
secretlen = strlen(secret);
|
||||
memcpy(packet + len, secret, secretlen);
|
||||
md5_calc(calc_digest, (u_char *)packet, len + secretlen);
|
||||
/* hash the reply, then the secret: nothing is written past the reply */
|
||||
if(!(ctx = mdh_init(MDH_MD5, 16))) return 2;
|
||||
if(!mdh_update(ctx, packet, len) || !mdh_update(ctx, secret, secretlen) ||
|
||||
!mdh_final(ctx, calc_digest, &dlen)) {
|
||||
mdh_free(ctx);
|
||||
return 2;
|
||||
}
|
||||
mdh_free(ctx);
|
||||
|
||||
/*
|
||||
* Return 0 if OK, 2 if not OK.
|
||||
@ -298,6 +306,7 @@ int radsend(struct clientparam * param, int auth, int stop){
|
||||
int total_length;
|
||||
int len;
|
||||
PROXYSOCKADDRTYPE saremote;
|
||||
PROXYSOCKADDRTYPE safrom;
|
||||
struct pollfd fds[1];
|
||||
char vector[AUTH_VECTOR_LEN];
|
||||
radius_packet_t packet, rpacket;
|
||||
@ -568,15 +577,25 @@ int radsend(struct clientparam * param, int auth, int stop){
|
||||
continue;
|
||||
}
|
||||
|
||||
salen = sizeof(saremote);
|
||||
|
||||
salen = sizeof(safrom);
|
||||
|
||||
data_len = so._recvfrom(so.state, remsock, (char *)&rpacket, sizeof(packet)-16,
|
||||
0, (struct sockaddr *)&saremote, &salen);
|
||||
0, (struct sockaddr *)&safrom, &salen);
|
||||
|
||||
|
||||
if (data_len < 20) {
|
||||
continue;
|
||||
}
|
||||
/* the reply must come from the server we asked */
|
||||
if(*SAFAMILY(&safrom) != *SAFAMILY(&saremote) ||
|
||||
memcmp(SAADDR(&safrom), SAADDR(&saremote), SAADDRLEN(&saremote)) ||
|
||||
*SAPORT(&safrom) != *SAPORT(&saremote)) {
|
||||
continue;
|
||||
}
|
||||
/* and answer this request, and be of its own length */
|
||||
if(data_len != ntohs(rpacket.length) || rpacket.id != packet.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if( auth && rpacket.code != PW_AUTHENTICATION_ACK &&
|
||||
rpacket.code != PW_AUTHENTICATION_REJECT ){
|
||||
|
||||
@ -61,14 +61,9 @@ char **load_string(FILE *f,int max_count_str, int *countloadstr,
|
||||
|
||||
if (strstr(tmpbuf1,"[end]")==NULL)
|
||||
{
|
||||
/* find and replace \n \r*/
|
||||
rstr = tmpbuf1;
|
||||
while (*rstr!='\0')
|
||||
{
|
||||
if (*rstr=='\r' || *rstr=='\n' )
|
||||
{ *rstr='\0'; rstr++; }
|
||||
rstr++;
|
||||
}
|
||||
/* the line's end cut off: what the file says with \n is what is
|
||||
sent */
|
||||
tmpbuf1[strcspn(tmpbuf1, "\r\n")] = '\0';
|
||||
|
||||
while ((rstr=strstr(tmpbuf1,"\\n")))
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -167,6 +167,7 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
|
||||
k += 4;
|
||||
if(na > 255) na = 255; /* somebody is very evil */
|
||||
for (j = 0; j < na; j++) { /* now there should be answers */
|
||||
if(k >= len) break;
|
||||
while(buf[k] < 192 && buf[k] !=0 && (k+buf[k]+14) < len) k+= (buf[k] + 1);
|
||||
if(!buf[k]) k--;
|
||||
if((k+(af == AF_INET6?28:16)) > len) {
|
||||
@ -195,15 +196,17 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
|
||||
k+= (12 + flen);
|
||||
continue; /* we need A PTR */
|
||||
}
|
||||
for (s2 = buf + k + 12; s2 < (buf + k + 12 + len) && *s2; ){
|
||||
s1 = s2 + ((unsigned)*s2) + 1;
|
||||
s1 = buf + k + 12 + flen; /* the end of this record's data, inside the answer */
|
||||
for (s2 = buf + k + 12; s2 < s1 && *s2 && *s2 < 64; ){
|
||||
unsigned char *next = s2 + ((unsigned)*s2) + 1;
|
||||
*s2 = '.';
|
||||
s2 = s1;
|
||||
s2 = next;
|
||||
}
|
||||
*s2 = 0;
|
||||
if(s2 >= s1 || *s2 || s2 == buf + k + 12)
|
||||
break; /* the name runs past its data, is compressed or empty */
|
||||
if(param->username)free(param->username);
|
||||
param->username = (unsigned char *)strdup ((char *)buf + k + 13);
|
||||
|
||||
|
||||
return udpresolve(af,param->username, value, NULL, NULL, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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