近期由于需要使用到pybot来执行调度过来的自动化脚本,但是脚本类型多样,参数多样,因此写了一个抽象的pybot的执行类
@Data
@Slf4j
public abstract class AbstractPybotServer extends Observable {
protected static final String outputXmlName = "output.xml";
protected static final String logHtmlName = "log.html";
/**
* pybot额外的参数
*/
protected Map<String, String> additional;
/**
* suit列表
*/
protected List<String> caseFullPath;
/**
* pybot执行的参数
*/
private List<String> pybotParameter;
/**
* 重试次数,计数器
*/
protected Integer retryNum = 0;
/**
* 是否合并
*/
protected Boolean marge = false;
/**
* 存储报告文件栈
*/
protected Stack<String> outPutXmlStack = new Stack<>();
/**
* 观察者
*/
protected Suit suit;
/**
* 任务信息
*/
protected Appium appium;
protected LogProcessing logProcessing;
public AbstractPybotServer(Suit suit, Observer... observers) {
this(suit, null, observers);
}
public AbstractPybotServer(Suit suit, LogProcessing appiumListener, Observer... observers) {
this.suit = suit;
this.logProcessing = appiumListener;
addObserver(observers);
}
private void addObserver(Observer... observers) {
if (observers != null) {
for (Observer observer : observers) {
// 添加观察者
addObserver(observer);
}
}
}
/**
* 初始化方法,需要自定义
*/
protected abstract void init();
public void run() throws IOException {
// 初始化配置
init();
// 初始化默认参数
addDefaultAdditionalParameter();
String currRunDir = saveReportPathTimes();
firstRun(currRunDir);
caseFullPath = suit.getCaseFullPath();
boolean success = reportSave2FastDfs(currRunDir);
notifyObservers("第一次执行结束,判断是否重跑: success=[{}] ableRetry=[{}] maxRetryTimes=[{}] retryNum=[{}]",
success, ableRetry(), maxRetryTimes(), retryNum);
// 如果没有成功,则重跑,否则直接结束
while (!success && ableRetry()) {
addDefaultAdditionalParameter();
retryRun((currRunDir = saveReportPathTimes()), outPutXmlStack.peek());
success = reportSave2FastDfs(currRunDir);
notifyObservers("第{}次执行结束,判断是否重跑: success=[{}] ableRetry=[{}] maxRetryTimes=[{}] retryNum=[{}]",
retryNum, success, ableRetry(), maxRetryTimes(), retryNum);
}
// 合并报告
margeReport(saveReportPath(), outPutXmlStack);
reportSave2FastDfs(saveReportPath());
}
/**
* 返回值表示这次任务执行是否还可以重试运行
*
* @return 判断是否还可以重试执行
*/
protected abstract boolean ableRetry();
/**
* 最大的重试次数
*
* @return 最多可以执行多少次
*/
protected abstract Integer maxRetryTimes();
/**
* 第一次运行
*
* @param dir 指定报告存储目录
* @return out put xml存储地址
* @throws IOException 执行异常
*/
private void firstRun(String dir) throws IOException {
String outputXmlPath = reportPath(dir);
String cmd = runCommand(dir, null);
notifyObservers("第一次执行命令为:[{}]", cmd);
CmdUtil.runCommand(StrUtil.format("第1次执行", ++retryNum), cmd, new CmdMonitor() {
@Override
public void message(String message) {
notifyObservers(message);
}
@Override
public void process(Process process) {
updateProcess(process);
}
});
haveFile(outputXmlPath);
}
/**
* 更新当前任务执行的进程对象
*
* @param process 进程对象
*/
protected abstract void updateProcess(Process process);
/**
* 根据之前的xml报告进行重跑
*
* @param dir 指定报告存储目录
* @param retryReport 依据报告
* @throws IOException 执行异常
*/
private void retryRun(String dir, String retryReport) throws IOException {
String outputXmlPath = reportPath(dir);
String cmd = runCommand(dir, retryReport);
notifyObservers("重跑执行命令为:[{}]", cmd);
CmdUtil.runCommand(StrUtil.format("第{}次执行", ++retryNum), cmd, new CmdMonitor() {
@Override
public void message(String message) {
notifyObservers(message);
}
@Override
public void process(Process process) {
updateProcess(process);
}
});
haveFile(outputXmlPath);
}
/**
* 执行合并报告的动作
*
* @param margeDir 存储最终报告的目录
* @param reports 之前的重跑运行的报告地址
* @throws IOException 执行异常
*/
private void margeReport(String margeDir, Collection<String> reports) throws IOException {
marge = true;
String margeReportPath = reportPath(margeDir);
int retryReportNum = 0;
if (CollUtil.isNotEmpty(reports)) {
retryReportNum = reports.size();
}
String cmd = margeReportCommand(margeDir, reports);
notifyObservers("合并命令为:[{}]", cmd);
CmdUtil.runCommand(StrUtil.format("合并报告,一共运行了[{}]次,获取了[{}]个报告", retryNum, retryReportNum),
cmd, this::notifyObservers);
haveFile(margeReportPath);
}
/**
* 判断任务是否成功
*
* @param outputXmlPath 报告地址
* @return true成功 false失败
*/
protected boolean isSuccess(String outputXmlPath) {
if (StrUtil.isNotBlank(outputXmlPath) && (new File(outputXmlPath)).exists()) {
try {
TestReport testReport = TestReport.xml2Bean(outputXmlPath);
return testReport.success();
} catch (Exception e) {
return false;
}
}
return false;
}
/**
* 存储报告
*
* @param dir 报告存在的目录
*/
protected abstract boolean reportSave2FastDfs(String dir);
/**
* @param margePath 存储合并报告的地址
* @param reports 历史运行报告
* @return 合并pybot报告的命令
*/
private String margeReportCommand(String margePath, Collection<String> reports) {
StringBuilder result = new StringBuilder();
String command = StrUtil.format("rebot -o {}/output.xml -l {}/log -r {}/report -R ",
margePath, margePath, margePath);
result.append(command);
if (!CollUtil.isEmpty(reports)) {
for (String path : reports) {
result.append(path).append(" ");
}
}
return result.toString();
}
/**
* 如果文件存在,则返回文件地址,否则返回null
*
* @param filePath 检测的文件地址
* @return 文件地址
*/
private void haveFile(String filePath) {
File file = new File(filePath);
if (file.exists()) {
return;
}
notifyObservers("文件[{}]不存在", filePath);
}
/**
* @param dir 存储报告的目录
* @return 实际报告的地址
*/
private String reportPath(String dir) {
return StrUtil.format("{}/{}", dir, outputXmlName);
}
/**
* 第一个参数为重跑参数
* 第二个参数为额外附加参数
* 第三个参数为pybot命令额外功能需要使用的参数
* 第四个参数为报告存储地址
* 第五个参数为指定的suit地址
*
* @param saveReportXmlPath 报告存储地址
* @param retryReport 根据该路径下的报告进行重跑
* @return pybot执行命令
*/
private String runCommand(String saveReportXmlPath, String retryReport) {
return StrUtil.format("pybot {} {} {} -d {} {}",
retryParameter(retryReport),
additionalCommand(),
addPybotCommand(),
saveReportXmlPath,
caseCollStr());
}
/**
* @return 拼装重试的命令
*/
private String retryParameter(String preXmlReport) {
if (StrUtil.isEmpty(preXmlReport)) {
return "";
}
if (StrUtil.isNotBlank(preXmlReport)) {
return StrUtil.format(" -R {} ", preXmlReport);
}
return "";
}
/**
* @return 获取case的集合
*/
private String caseCollStr() {
StringBuilder caseColl = new StringBuilder();
caseFullPath.forEach(casePath -> caseColl.append(casePath).append(" "));
return caseColl.toString();
}
/**
* @return 拼装额外参数的命令
*/
private String additionalCommand() {
if (CollUtil.isEmpty(additional)) {
return "";
}
StringBuilder additionalStr = new StringBuilder();
additional.forEach((name, value) -> additionalStr.append("-v ").append(name).append(":").append(value).append(" "));
return additionalStr.toString();
}
/**
* 添加额外参数
*
* @param key key
* @param value value
*/
protected void addAdditionalParameter(String key, String value) {
if (CollUtil.isEmpty(additional)) {
additional = new HashMap<>();
}
additional.put(key, value);
}
private String addPybotCommand() {
if (CollUtil.isEmpty(pybotParameter)) {
return "";
}
StringBuilder pybotParameterStr = new StringBuilder();
pybotParameter.forEach(parameter -> pybotParameterStr.append(" ").append(parameter).append(" "));
return pybotParameterStr.toString();
}
/**
* 添加pybot命令的参数
*
* @param parameter 完整参数
*/
protected void addPybotParameter(String parameter) {
if (StrUtil.isNotBlank(parameter)) {
if (pybotParameter == null) {
pybotParameter = new ArrayList<>();
}
pybotParameter.add(parameter);
}
}
/**
* 添加运行参数
*/
protected abstract void addDefaultAdditionalParameter();
/**
* 保存报告文件
*
* @return 存储当前任务的地址
*/
protected abstract String saveReportPath();
/**
* @return 第几次运行的存储地址
*/
private String saveReportPathTimes() {
return StrUtil.format("{}/{}", saveReportPath(), retryNum);
}
@Override
public void notifyObservers(String message, Object... args) {
setChanged();
message = StrUtil.format(message, args);
super.notifyObservers(message);
}
@Override
public void notifyObservers(Object arg) {
setChanged();
super.notifyObservers(arg);
}
@Override
public void notifyObservers() {
setChanged();
super.notifyObservers();
}
}