Merge branch '3.0' into case/TS-4411-3.0

This commit is contained in:
menshibin 2024-01-22 09:13:21 +08:00 committed by GitHub
commit 7e080a9960
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 464 additions and 69 deletions

View File

@ -37,9 +37,6 @@
// This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
// until 00:00:00 January 1, 1970
static const uint64_t TIMEEPOCH = ((uint64_t)116444736000000000ULL);
// This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
// until 00:00:00 January 1, 1900
static const uint64_t TIMEEPOCH1900 = ((uint64_t)116445024000000000ULL);
/*
* We do not implement alternate representations. However, we always
@ -360,6 +357,7 @@ int32_t taosGetTimeOfDay(struct timeval *tv) {
t.QuadPart -= TIMEEPOCH;
tv->tv_sec = t.QuadPart / 10000000;
tv->tv_usec = (t.QuadPart % 10000000) / 10;
return 0;
#else
return gettimeofday(tv, NULL);
#endif
@ -482,33 +480,51 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf) {
sprintf(buf, "NaN");
}
return NULL;
}
} else if (*timep < 0) {
SYSTEMTIME ss, s;
FILETIME ff, f;
SYSTEMTIME s;
FILETIME f;
LARGE_INTEGER offset;
struct tm tm1;
time_t tt = 0;
if (localtime_s(&tm1, &tt) != 0) {
if (buf != NULL) {
sprintf(buf, "NaN");
LARGE_INTEGER offset;
struct tm tm1;
time_t tt = 0;
if (localtime_s(&tm1, &tt) != 0) {
if (buf != NULL) {
sprintf(buf, "NaN");
}
return NULL;
}
ss.wYear = tm1.tm_year + 1900;
ss.wMonth = tm1.tm_mon + 1;
ss.wDay = tm1.tm_mday;
ss.wHour = tm1.tm_hour;
ss.wMinute = tm1.tm_min;
ss.wSecond = tm1.tm_sec;
ss.wMilliseconds = 0;
SystemTimeToFileTime(&ss, &ff);
offset.QuadPart = ff.dwHighDateTime;
offset.QuadPart <<= 32;
offset.QuadPart |= ff.dwLowDateTime;
offset.QuadPart += *timep * 10000000;
f.dwLowDateTime = offset.QuadPart & 0xffffffff;
f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff;
FileTimeToSystemTime(&f, &s);
result->tm_sec = s.wSecond;
result->tm_min = s.wMinute;
result->tm_hour = s.wHour;
result->tm_mday = s.wDay;
result->tm_mon = s.wMonth - 1;
result->tm_year = s.wYear - 1900;
result->tm_wday = s.wDayOfWeek;
result->tm_yday = 0;
result->tm_isdst = 0;
} else {
if (localtime_s(result, timep) != 0) {
if (buf != NULL) {
sprintf(buf, "NaN");
}
return NULL;
}
return NULL;
}
offset.QuadPart = TIMEEPOCH1900;
offset.QuadPart += *timep * 10000000;
f.dwLowDateTime = offset.QuadPart & 0xffffffff;
f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff;
FileTimeToSystemTime(&f, &s);
result->tm_sec = s.wSecond;
result->tm_min = s.wMinute;
result->tm_hour = s.wHour;
result->tm_mday = s.wDay;
result->tm_mon = s.wMonth - 1;
result->tm_year = s.wYear - 1900;
result->tm_wday = s.wDayOfWeek;
result->tm_yday = 0;
result->tm_isdst = 0;
#else
res = localtime_r(timep, result);
if (res == NULL && buf != NULL) {

View File

@ -0,0 +1,105 @@
import taos
import sys
import os
import subprocess
import glob
import shutil
import time
from frame.log import *
from frame.cases import *
from frame.sql import *
from frame.caseBase import *
from frame import *
from frame.autogen import *
from frame.server.dnodes import *
from frame.server.cluster import *
class TDTestCase(TBase):
def init(self, conn, logSql, replicaVar=3):
super(TDTestCase, self).init(conn, logSql, replicaVar=3, db="snapshot", checkColName="c1")
self.valgrind = 0
self.childtable_count = 10
# tdSql.init(conn.cursor())
tdSql.init(conn.cursor(), logSql) # output sql.txt file
def run(self):
tdSql.prepare()
autoGen = AutoGen()
autoGen.create_db(self.db, 2, 3)
tdSql.execute(f"use {self.db}")
autoGen.create_stable(self.stb, 5, 10, 8, 8)
autoGen.create_child(self.stb, "d", self.childtable_count)
autoGen.insert_data(1000)
tdSql.execute(f"flush database {self.db}")
clusterDnodes.stoptaosd(3)
# clusterDnodes.stoptaosd(1)
# clusterDnodes.starttaosd(3)
# time.sleep(5)
# clusterDnodes.stoptaosd(2)
# clusterDnodes.starttaosd(1)
# time.sleep(5)
autoGen.insert_data(5000, True)
tdSql.execute(f"flush database {self.db}")
# sql = 'show vnodes;'
# while True:
# bFinish = True
# param_list = tdSql.query(sql, row_tag=True)
# for param in param_list:
# if param[3] == 'leading' or param[3] == 'following':
# bFinish = False
# break
# if bFinish:
# break
self.snapshotAgg()
time.sleep(10)
clusterDnodes.stopAll()
for i in range(1, 4):
path = clusterDnodes.getDnodeDir(i)
dnodesRootDir = os.path.join(path,"data","vnode", "vnode*")
dirs = glob.glob(dnodesRootDir)
for dir in dirs:
if os.path.isdir(dir):
tdLog.debug("delete dir: %s " % (dnodesRootDir))
self.remove_directory(os.path.join(dir, "wal"))
clusterDnodes.starttaosd(1)
clusterDnodes.starttaosd(2)
clusterDnodes.starttaosd(3)
sql = "show vnodes;"
time.sleep(10)
while True:
bFinish = True
param_list = tdSql.query(sql, row_tag=True)
for param in param_list:
if param[3] == 'offline':
tdLog.exit(
"dnode synchronous fail dnode id: %d, vgroup id:%d status offline" % (param[0], param[1]))
if param[3] == 'leading' or param[3] == 'following':
bFinish = False
break
if bFinish:
break
self.timestamp_step = 1
self.insert_rows = 6000
self.checkInsertCorrect()
self.checkAggCorrect()
def remove_directory(self, directory):
try:
shutil.rmtree(directory)
tdLog.debug("delete dir: %s " % (directory))
except OSError as e:
tdLog.exit("delete fail dir: %s " % (directory))
def stop(self):
tdSql.close()
tdLog.success(f"{__file__} successfully executed")
tdCases.addLinux(__file__, TDTestCase())
tdCases.addWindows(__file__, TDTestCase())

View File

@ -0,0 +1,61 @@
{
"filetype": "insert",
"cfgdir": "/etc/taos",
"host": "127.0.0.1",
"port": 6030,
"user": "root",
"password": "taosdata",
"connection_pool_size": 8,
"num_of_records_per_req": 4000,
"prepared_rand": 10000,
"thread_count": 3,
"create_table_thread_count": 1,
"confirm_parameter_prompt": "no",
"databases": [
{
"dbinfo": {
"name": "db",
"drop": "no",
"vgroups": 3,
"replica": 3,
"duration":"3d",
"wal_retention_period": 1,
"wal_retention_size": 1,
"stt_trigger": 1
},
"super_tables": [
{
"name": "stb",
"child_table_exists": "yes",
"childtable_count": 6,
"insert_rows": 50000,
"childtable_prefix": "d",
"insert_mode": "taosc",
"timestamp_step": 60000,
"start_timestamp":1700000000000,
"columns": [
{ "type": "bool", "name": "bc"},
{ "type": "float", "name": "fc" },
{ "type": "double", "name": "dc"},
{ "type": "tinyint", "name": "ti"},
{ "type": "smallint", "name": "si" },
{ "type": "int", "name": "ic" },
{ "type": "bigint", "name": "bi" },
{ "type": "utinyint", "name": "uti"},
{ "type": "usmallint", "name": "usi"},
{ "type": "uint", "name": "ui" },
{ "type": "ubigint", "name": "ubi"},
{ "type": "binary", "name": "bin", "len": 8},
{ "type": "nchar", "name": "nch", "len": 16}
],
"tags": [
{"type": "tinyint", "name": "groupid","max": 10,"min": 1},
{"name": "location","type": "binary", "len": 16, "values":
["San Francisco", "Los Angles", "San Diego", "San Jose", "Palo Alto", "Campbell", "Mountain View","Sunnyvale", "Santa Clara", "Cupertino"]
}
]
}
]
}
]
}

View File

@ -32,7 +32,7 @@
"childtable_prefix": "d",
"insert_mode": "taosc",
"timestamp_step": 30000,
"start_timestamp":"2023-10-01 10:00:00",
"start_timestamp":1700000000000,
"columns": [
{ "type": "bool", "name": "bc"},
{ "type": "float", "name": "fc" },

View File

@ -34,23 +34,170 @@ class TDTestCase(TBase):
"querySmaOptimize": "1"
}
def insertData(self):
tdLog.info(f"insert data.")
# taosBenchmark run
jfile = etool.curFile(__file__, "query_basic.json")
etool.benchMark(json=jfile)
etool.benchMark(json = jfile)
tdSql.execute(f"use {self.db}")
tdSql.execute("select database();")
# set insert data information
# come from query_basic.json
self.childtable_count = 6
self.insert_rows = 100000
self.timestamp_step = 30000
self.start_timestamp = 1700000000000
# write again disorder
self.flushDb()
jfile = etool.curFile(__file__, "cquery_basic.json")
etool.benchMark(json = jfile)
def genTime(self, preCnt, cnt):
start = self.start_timestamp + preCnt * self.timestamp_step
end = start + self.timestamp_step * cnt
return (start, end)
def doWindowQuery(self):
pre = f"select count(ts) from {self.stb} "
# case1 operator "in" "and" is same
cnt = 6000
s,e = self.genTime(12000, cnt)
sql1 = f"{pre} where ts between {s} and {e} "
sql2 = f"{pre} where ts >= {s} and ts <={e} "
expectCnt = (cnt + 1) * self.childtable_count
tdSql.checkFirstValue(sql1, expectCnt)
tdSql.checkFirstValue(sql2, expectCnt)
# case2 no overloap "or" left
cnt1 = 120
s1, e1 = self.genTime(4000, cnt1)
cnt2 = 3000
s2, e2 = self.genTime(10000, cnt2)
sql = f"{pre} where (ts >= {s1} and ts < {e1}) or (ts >= {s2} and ts < {e2})"
expectCnt = (cnt1 + cnt2) * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
# case3 overloap "or" right
cnt1 = 300
s1, e1 = self.genTime(17000, cnt1)
cnt2 = 8000
s2, e2 = self.genTime(70000, cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) or (ts > {s2} and ts <= {e2})"
expectCnt = (cnt1 + cnt2) * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
# case4 overloap "or"
cnt1 = 1000
s1, e1 = self.genTime(9000, cnt1)
cnt2 = 1000
s2, e2 = self.genTime(9000 + 500 , cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) or (ts > {s2} and ts <= {e2})"
expectCnt = (cnt1 + 500) * self.childtable_count # expect=1500
tdSql.checkFirstValue(sql, expectCnt)
# case5 overloap "or" boundary hollow->solid
cnt1 = 3000
s1, e1 = self.genTime(45000, cnt1)
cnt2 = 2000
s2, e2 = self.genTime(45000 + cnt1 , cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) or (ts > {s2} and ts <= {e2})"
expectCnt = (cnt1+cnt2) * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
# case6 overloap "or" boundary solid->solid
cnt1 = 300
s1, e1 = self.genTime(55000, cnt1)
cnt2 = 500
s2, e2 = self.genTime(55000 + cnt1 , cnt2)
sql = f"{pre} where (ts >= {s1} and ts <= {e1}) or (ts >= {s2} and ts <= {e2})"
expectCnt = (cnt1+cnt2+1) * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
# case7 overloap "and"
cnt1 = 1000
s1, e1 = self.genTime(40000, cnt1)
cnt2 = 1000
s2, e2 = self.genTime(40000 + 500 , cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) and (ts > {s2} and ts <= {e2})"
expectCnt = cnt1/2 * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
# case8 overloap "and" boundary hollow->solid solid->hollow
cnt1 = 3000
s1, e1 = self.genTime(45000, cnt1)
cnt2 = 2000
s2, e2 = self.genTime(45000 + cnt1 , cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) and (ts >= {s2} and ts < {e2})"
expectCnt = 1 * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
# case9 no overloap "and"
cnt1 = 6000
s1, e1 = self.genTime(20000, cnt1)
cnt2 = 300
s2, e2 = self.genTime(70000, cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) and (ts >= {s2} and ts <= {e2})"
expectCnt = 0
tdSql.checkFirstValue(sql, expectCnt)
# case10 cnt1 contain cnt2 and
cnt1 = 5000
s1, e1 = self.genTime(25000, cnt1)
cnt2 = 400
s2, e2 = self.genTime(28000, cnt2)
sql = f"{pre} where (ts > {s1} and ts <= {e1}) and (ts >= {s2} and ts < {e2})"
expectCnt = cnt2 * self.childtable_count
tdSql.checkFirstValue(sql, expectCnt)
def queryMax(self, colname):
sql = f"select max({colname}) from {self.stb}"
tdSql.query(sql)
return tdSql.getData(0, 0)
def checkMax(self):
# max for tsdbRetrieveDatablockSMA2 coverage
colname = "ui"
max = self.queryMax(colname)
# insert over max
sql = f"insert into d0(ts, {colname}) values"
for i in range(1, 5):
sql += f" (now + {i}s, {max+i})"
tdSql.execute(sql)
self.flushDb()
expectMax = max + 4
for i in range(1, 5):
realMax = self.queryMax(colname)
if realMax != expectMax:
tdLog.exit(f"Max value not expect. expect:{expectMax} real:{realMax}")
# query ts list
sql = f"select ts from d0 where ui={expectMax}"
tdSql.query(sql)
tss = tdSql.getColData(0)
for ts in tss:
# delete
sql = f"delete from d0 where ts = '{ts}'"
tdSql.execute(sql)
expectMax -= 1
self.checkInsertCorrect()
def doQuery(self):
tdLog.info(f"do query.")
self.doWindowQuery()
# max
self.checkMax()
# __group_key
sql = f"select count(*),_group_key(uti),uti from {self.stb} partition by uti"
tdSql.query(sql)
@ -76,6 +223,20 @@ class TDTestCase(TBase):
sql2 = "select bi from stb where bi is not null order by bi desc limit 10;"
self.checkSameResult(sql1, sql2)
# distributed expect values
expects = {
"Block_Rows" : 6*100000,
"Total_Tables" : 6,
"Total_Vgroups" : 3
}
self.waitTransactionZero()
reals = self.getDistributed(self.stb)
for k in expects.keys():
v = expects[k]
if int(reals[k]) != v:
tdLog.exit(f"distribute {k} expect: {v} real: {reals[k]}")
# run
def run(self):
tdLog.debug(f"start to excute {__file__}")

View File

@ -162,14 +162,19 @@ class AutoGen:
tdLog.info(f" insert data i={i}")
values = ""
tdLog.info(f" insert child data {child_name} finished, insert rows={cnt}")
tdLog.info(f" insert child data {child_name} finished, insert rows={cnt}")
return ts
# insert data
def insert_data(self, cnt):
def insert_data(self, cnt, bContinue=False):
if not bContinue:
self.ts = 1600000000000
currTs = 1600000000000
for i in range(self.child_cnt):
name = f"{self.child_name}{i}"
self.insert_data_child(name, cnt, self.batch_size, 1)
currTs = self.insert_data_child(name, cnt, self.batch_size, 1)
self.ts = currTs
tdLog.info(f" insert data ok, child table={self.child_cnt} insert rows={cnt}")
# insert same timestamp to all childs

View File

@ -29,7 +29,7 @@ class TBase:
#
# init
def init(self, conn, logSql, replicaVar=1):
def init(self, conn, logSql, replicaVar=1, db="db", stb="stb", checkColName="ic"):
# save param
self.replicaVar = int(replicaVar)
tdSql.init(conn.cursor(), True)
@ -41,14 +41,14 @@ class TBase:
self.mLevelDisk = 0
# test case information
self.db = "db"
self.stb = "stb"
self.db = db
self.stb = stb
# sql
self.sqlSum = f"select sum(ic) from {self.stb}"
self.sqlMax = f"select max(ic) from {self.stb}"
self.sqlMin = f"select min(ic) from {self.stb}"
self.sqlAvg = f"select avg(ic) from {self.stb}"
self.sqlSum = f"select sum({checkColName}) from {self.stb}"
self.sqlMax = f"select max({checkColName}) from {self.stb}"
self.sqlMin = f"select min({checkColName}) from {self.stb}"
self.sqlAvg = f"select avg({checkColName}) from {self.stb}"
self.sqlFirst = f"select first(ts) from {self.stb}"
self.sqlLast = f"select last(ts) from {self.stb}"
@ -136,7 +136,7 @@ class TBase:
tdSql.checkAgg(sql, self.childtable_count)
# check step
sql = f"select * from (select diff(ts) as dif from {self.stb} partition by tbname) where dif != {self.timestamp_step}"
sql = f"select * from (select diff(ts) as dif from {self.stb} partition by tbname order by ts desc) where dif != {self.timestamp_step}"
tdSql.query(sql)
tdSql.checkRows(0)
@ -229,9 +229,9 @@ class TBase:
#
# get vgroups
def getVGroup(self, db_name):
def getVGroup(self, dbName):
vgidList = []
sql = f"select vgroup_id from information_schema.ins_vgroups where db_name='{db_name}'"
sql = f"select vgroup_id from information_schema.ins_vgroups where db_name='{dbName}'"
res = tdSql.getResult(sql)
rows = len(res)
for i in range(rows):
@ -239,6 +239,29 @@ class TBase:
return vgidList
# get distributed rows
def getDistributed(self, tbName):
sql = f"show table distributed {tbName}"
tdSql.query(sql)
dics = {}
i = 0
for i in range(tdSql.getRows()):
row = tdSql.getData(i, 0)
#print(row)
row = row.replace('[', '').replace(']', '')
#print(row)
items = row.split(' ')
#print(items)
for item in items:
#print(item)
v = item.split('=')
#print(v)
if len(v) == 2:
dics[v[0]] = v[1]
if i > 5:
break
print(dics)
return dics
#
@ -269,3 +292,15 @@ class TBase:
if len(lists) == 0:
tdLog.exit(f"list is empty {tips}")
#
# str util
#
# covert list to sql format string
def listSql(self, lists, sepa = ","):
strs = ""
for ls in lists:
if strs != "":
strs += sepa
strs += f"'{ls}'"
return strs

View File

@ -13,23 +13,24 @@ from frame.common import *
class ClusterDnodes(TDDnodes):
"""rewrite TDDnodes and make MyDdnodes as TDDnodes child class"""
def __init__(self ,dnodes_lists):
def __init__(self):
super(ClusterDnodes,self).__init__()
self.dnodes = dnodes_lists # dnode must be TDDnode instance
self.simDeployed = False
self.testCluster = False
self.valgrind = 0
self.killValgrind = 1
def init(self, dnodes_lists, deployPath, masterIp):
self.dnodes = dnodes_lists # dnode must be TDDnode instance
super(ClusterDnodes, self).init(deployPath, masterIp)
clusterDnodes = ClusterDnodes()
class ConfigureyCluster:
"""This will create defined number of dnodes and create a cluster.
at the same time, it will return TDDnodes list: dnodes, """
hostname = socket.gethostname()
def __init__(self):
self.dnodes = []
self.dnodes = []
self.dnodeNums = 5
self.independent = True
self.startPort = 6030

View File

@ -251,6 +251,11 @@ class TDDnodes:
dnodesRootDir = "%s/sim" % (self.path)
return dnodesRootDir
def getDnodeDir(self, index):
self.check(index)
dnodesDir = "%s/sim/dnode%d" % (self.path, index)
return dnodesDir
def getSimCfgPath(self):
return self.sim.getCfgDir()

View File

@ -223,6 +223,12 @@ class TDSql:
def getData(self, row, col):
self.checkRowCol(row, col)
return self.res[row][col]
def getColData(self, col):
colDatas = []
for i in range(self.queryRows):
colDatas.append(self.res[i][col])
return colDatas
def getResult(self, sql):
self.sql = sql

View File

@ -405,15 +405,15 @@ if __name__ == "__main__":
else :
tdLog.debug("create an cluster with %s nodes and make %s dnode as independent mnode"%(dnodeNums,mnodeNums))
dnodeslist = cluster.configure_cluster(dnodeNums=dnodeNums, mnodeNums=mnodeNums, independentMnode=independentMnode, level=level, disk=disk)
tdDnodes = ClusterDnodes(dnodeslist)
tdDnodes.init(deployPath, masterIp)
tdDnodes.setTestCluster(testCluster)
tdDnodes.setValgrind(valgrind)
tdDnodes.stopAll()
for dnode in tdDnodes.dnodes:
tdDnodes.deploy(dnode.index, updateCfgDict)
for dnode in tdDnodes.dnodes:
tdDnodes.starttaosd(dnode.index)
clusterDnodes.init(dnodeslist, deployPath, masterIp)
clusterDnodes.setTestCluster(testCluster)
clusterDnodes.setValgrind(valgrind)
clusterDnodes.setAsan(asan)
clusterDnodes.stopAll()
for dnode in clusterDnodes.dnodes:
clusterDnodes.deploy(dnode.index, updateCfgDict)
for dnode in clusterDnodes.dnodes:
clusterDnodes.starttaosd(dnode.index)
tdCases.logSql(logSql)
if restful or websocket:
@ -580,16 +580,15 @@ if __name__ == "__main__":
print(independentMnode,"independentMnode valuse")
# create dnode list
dnodeslist = cluster.configure_cluster(dnodeNums=dnodeNums, mnodeNums=mnodeNums, independentMnode=independentMnode, level=level, disk=disk)
tdDnodes = ClusterDnodes(dnodeslist)
tdDnodes.init(deployPath, masterIp)
tdDnodes.setTestCluster(testCluster)
tdDnodes.setValgrind(valgrind)
tdDnodes.setAsan(asan)
tdDnodes.stopAll()
for dnode in tdDnodes.dnodes:
tdDnodes.deploy(dnode.index,updateCfgDict)
for dnode in tdDnodes.dnodes:
tdDnodes.starttaosd(dnode.index)
clusterDnodes.init(dnodeslist, deployPath, masterIp)
clusterDnodes.setTestCluster(testCluster)
clusterDnodes.setValgrind(valgrind)
clusterDnodes.setAsan(asan)
clusterDnodes.stopAll()
for dnode in clusterDnodes.dnodes:
clusterDnodes.deploy(dnode.index,updateCfgDict)
for dnode in clusterDnodes.dnodes:
clusterDnodes.starttaosd(dnode.index)
tdCases.logSql(logSql)
if restful or websocket:

View File

@ -18,6 +18,7 @@ fi
,,y,army,./pytest.sh python3 ./test.py -f enterprise/s3/s3_basic.py -L 3 -D 1
,,y,army,./pytest.sh python3 ./test.py -f community/cluster/snapshot.py -N 3 -L 3 -D 2
,,y,army,./pytest.sh python3 ./test.py -f community/query/fill/fill_desc.py -N 3 -L 3 -D 2
,,y,army,./pytest.sh python3 ./test.py -f community/cluster/incSnapshot.py -N 3 -L 3 -D 2
,,y,army,./pytest.sh python3 ./test.py -f community/query/query_basic.py -N 3
,,n,army,python3 ./test.py -f community/cmdline/fullopt.py