Skip to content

Commit

Permalink
[external commands]
Browse files Browse the repository at this point in the history
- [`tail(id,file)`]: simulate the *NIX tail command.

Signed-off-by: automike <mikeliucc@users.noreply.github.com>
  • Loading branch information
mikeliucc committed Jun 1, 2019
1 parent c2caf0f commit 5b599e4
Show file tree
Hide file tree
Showing 49 changed files with 128 additions and 32 deletions.
2 changes: 2 additions & 0 deletions src/main/java/org/nexial/commons/proc/ProcessInvoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.nexial.commons.utils.FileUtil;
import org.nexial.core.ShutdownAdvisor;
import org.nexial.core.utils.ConsoleUtils;

import static org.nexial.core.NexialConst.DEF_FILE_ENCODING;
Expand Down Expand Up @@ -146,6 +147,7 @@ public static ProcessOutcome invoke(String command, List<String> params, Map<Str
// here we go...
// jdk5-specific...
Process process = pb.start();
ShutdownAdvisor.addAdvisor(new ExternalProcessTerminator(process));

StreamGobbler stderr = new StreamGobbler(process.getErrorStream())
.setEnableConsole(enableConsole)
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/nexial/core/plugins/web/WebCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,12 @@ public StepResult selectWindowByIndexAndWait(String index, String waitMs) {
ensureReady();

Set<String> handles = driver.getWindowHandles();
ConsoleUtils.log("found " + CollectionUtils.size(handles) + " window handle(s); recalibrating again...");

// double check
try { Thread.sleep(1000);} catch (InterruptedException e) { }
handles = driver.getWindowHandles();

if (CollectionUtils.isEmpty(handles)) { return StepResult.fail("No window or windows handle found"); }

int handleCount = handles.size();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.nexial.commons.proc

import org.nexial.core.plugins.ForcefulTerminate

class ExternalProcessTerminator(var externalProc: Process?) : ForcefulTerminate {
override fun mustForcefullyTerminate() = externalProc != null && externalProc!!.isAlive

override fun forcefulTerminate() {
if (mustForcefullyTerminate()) {
println("forcefully destroying process $externalProc...")
externalProc!!.destroyForcibly()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,25 @@
package org.nexial.core.plugins.external

import org.apache.commons.io.IOUtils
import org.apache.commons.io.input.Tailer
import org.apache.commons.io.input.TailerListenerAdapter
import org.apache.commons.lang3.StringUtils
import org.junit.runner.JUnitCore
import org.nexial.commons.proc.ProcessInvoker
import org.nexial.commons.proc.ProcessInvoker.*
import org.nexial.commons.proc.RuntimeUtils
import org.nexial.commons.utils.FileUtil
import org.nexial.commons.utils.TextUtils
import org.nexial.core.NexialConst.*
import org.nexial.core.ShutdownAdvisor
import org.nexial.core.SystemVariables.getDefaultBool
import org.nexial.core.model.StepResult
import org.nexial.core.plugins.ForcefulTerminate
import org.nexial.core.plugins.base.BaseCommand
import org.nexial.core.utils.CheckUtils.requires
import org.nexial.core.utils.CheckUtils.requiresNotBlank
import org.nexial.core.utils.ConsoleUtils
import org.nexial.core.variable.Syspath
import java.io.File
import java.io.File.separator
import java.io.IOException
import java.lang.System.lineSeparator
Expand Down Expand Up @@ -106,7 +112,7 @@ class ExternalCommand : BaseCommand() {

val env = prepEnv(fileName, currentRow)

ProcessInvoker.invoke(programAndParams[0], programAndParams.filterIndexed { index, _ -> index > 0 }, env)
invoke(programAndParams[0], programAndParams.filterIndexed { index, _ -> index > 0 }, env)

//attach link to results
addLinkRef("Follow the link to view the output", "output", fileName)
Expand All @@ -133,7 +139,7 @@ class ExternalCommand : BaseCommand() {

val env = prepEnv(fileName, currentRow)

ProcessInvoker.invokeNoWait(programAndParams[0], programAndParams.filterIndexed { index, _ -> index > 0 }, env)
invokeNoWait(programAndParams[0], programAndParams.filterIndexed { index, _ -> index > 0 }, env)

//attach link to results
addLinkRef("Follow the link to view the output", "output", fileName)
Expand All @@ -144,6 +150,31 @@ class ExternalCommand : BaseCommand() {
}
}

/**
* tail a reachable (local or network via shared folder or SMB) file. File does not have to exists when this command
* is executed. However, background thread will be issued to watch/display the content of such file.
* @param file String
* @return StepResult
*/
fun tail(id: String, file: String): StepResult {
requiresNotBlank(id, "invalid id", id)
requiresNotBlank(file, "invalid file", file)

if (FileUtil.isFileReadable(file, 1)) {
ConsoleUtils.log("File $file not readable at this time. Nexial will display its content when available")
}

val listener = ExternalTailer(id)
val tailer = Tailer.create(File(file), listener, 250, true, true)

val tailThread = Thread(tailer)
tailThread.isDaemon = true
tailThread.start()

ShutdownAdvisor.addAdvisor(ExternalTailShutdownHelper(tailThread, tailer))
return StepResult.success("tail watch on $file began...")
}

private fun prepEnv(outputFile: String, currentRow: String): MutableMap<String, String> {
val env = mutableMapOf<String, String>()

Expand All @@ -168,3 +199,31 @@ class ExternalCommand : BaseCommand() {
}
}
}

class ExternalTailer(val id: String) : TailerListenerAdapter() {
override fun handle(line: String?) = ConsoleUtils.log(id, line)

override fun handle(ex: java.lang.Exception?) {
if (ex !is InterruptedException) ConsoleUtils.log(id, "ERROR FOUND:\n$ex")
}

override fun fileNotFound() {
super.fileNotFound()
}
}

class ExternalTailShutdownHelper(private var tailThread: Thread?, var tailer: Tailer?) : ForcefulTerminate {
override fun mustForcefullyTerminate() = (tailThread != null && tailThread!!.isAlive) || (tailer != null)

override fun forcefulTerminate() {
if (tailer != null) {
tailer!!.stop()
tailer = null
}

if (tailThread != null) {
tailThread!!.interrupt()
tailThread = null
}
}
}
45 changes: 19 additions & 26 deletions src/main/kotlin/org/nexial/core/tools/repairExcel/ExcelUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ object ExcelUtils {
val targetWorkbook = targetExcel.workbook
val excelAddress = fileType.excelAddress
try {
sourceSheets.forEach { sheet ->
copySheet(sheet.sheet, targetWorkbook, sheet.findLastDataRow(excelAddress), fileType)
}
sourceSheets.forEach { copySheet(it.sheet, targetWorkbook, it.findLastDataRow(excelAddress), fileType) }
} catch (e: Exception) {
log("Unable to proceed repair file ${file.absolutePath} further due to ${e.message}")
} finally {
Expand All @@ -102,11 +100,12 @@ object ExcelUtils {
val sheetName = sourceSheet.sheetName
val tempSheetIndex = targetWorkbook.getSheetIndex(TEMP_SHEET_NAME)
val targetSheet = targetWorkbook.cloneSheet(tempSheetIndex, sheetName)

// set maximum row limit to read data
val lastDataRow = if (lastDataRow < EXCEL_ROW_COL_MAX_LIMIT) lastDataRow else EXCEL_ROW_COL_MAX_LIMIT
val lastRow = Math.min(lastDataRow, EXCEL_ROW_COL_MAX_LIMIT)

var destRowIndex = 0
for (rowIndex in 0 until lastDataRow) {
for (rowIndex in 0 until lastRow) {
if (!RepairExcels.ignoreRowToCopy(fileType, rowIndex)) {
val sourceRow = getRow(sourceSheet, rowIndex)
// remove rows from data sheets if system variable is read only.
Expand All @@ -124,24 +123,18 @@ object ExcelUtils {
* @param newRow [XSSFRow] new row in the target sheet where source row to be copied
* @param fileType [ArtifactType] type of file. By default it is [DATA]
*/
fun copyRow(sourceRow: XSSFRow, newRow: XSSFRow, fileType: ArtifactType = DATA) {
var lastDataColumn = RepairExcels.lastColumnIdx(sourceRow, fileType)
private fun copyRow(sourceRow: XSSFRow, newRow: XSSFRow, fileType: ArtifactType = DATA) {
// set column index as max limit
if (lastDataColumn > EXCEL_ROW_COL_MAX_LIMIT) {
lastDataColumn = EXCEL_ROW_COL_MAX_LIMIT
}
val lastColumn = Math.min(RepairExcels.lastColumnIdx(sourceRow, fileType), EXCEL_ROW_COL_MAX_LIMIT)

for (columnIndex in 0 until lastDataColumn + 1) {
for (columnIndex in 0 until lastColumn + 1) {
val oldCell = sourceRow.getCell(columnIndex)
var newCell = newRow.getCell(columnIndex)

// If the old cell is null jump to next cell with new cell as empty
if (oldCell == null) {
// newCell = null
continue
} else {
if (newCell == null) newCell = newRow.createCell(columnIndex)
}
if (oldCell == null) continue

if (newCell == null) newCell = newRow.createCell(columnIndex)

// set cell value
when (oldCell.cellTypeEnum) {
Expand Down Expand Up @@ -201,15 +194,15 @@ object ExcelUtils {
}
}

val repairArtifactLog: RepairArtifactLog? =
try {
Excel.save(destFile, targetWorkbook)
action = "processed (changed)"
RepairArtifactLog(file, 0, backupOrPreviewLoc)
} catch (e: IOException) {
ConsoleUtils.error("Unable to repair excel as ${e.message}")
null
}
val repairArtifactLog: RepairArtifactLog? = try {
Excel.save(destFile, targetWorkbook)
action = "processed (changed)"
RepairArtifactLog(file, 0, backupOrPreviewLoc)
} catch (e: IOException) {
ConsoleUtils.error("Unable to repair excel as ${e.message}")
null
}

log(action, file)
return repairArtifactLog
}
Expand Down
12 changes: 9 additions & 3 deletions src/test/resources/showcase/artifact/data/dummy.jmx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">10</stringProp>
<stringProp name="LoopController.loops">30</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">5</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.delayedStart">true</boolProp>
</ThreadGroup>
<hashTree>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="test data" enabled="true">
Expand All @@ -62,7 +63,7 @@
<hashTree/>
<TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="activity" enabled="true"/>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true">
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="search_craigslist" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="query" elementType="HTTPArgument">
Expand All @@ -85,11 +86,16 @@
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="1sec_wait" enabled="true">
<stringProp name="ConstantTimer.delay">1000</stringProp>
</ConstantTimer>
<hashTree/>
</hashTree>
</hashTree>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
Expand Down
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/base-showcase.xlsx
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/disable-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/electron-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/excel-showcase.xlsx
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/external-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/failafter-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/image-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/io-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/jmeter-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/json-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/mail-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/number-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/redis-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/sqs-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/step-showcase.xlsx
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/web-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/ws.async-showcase.xlsx
Binary file not shown.
Binary file modified src/test/resources/showcase/artifact/script/xml-showcase.xlsx
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/unittesting/artifact/script/unitTest_excel.xlsx
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/unittesting/artifact/script/unitTest_io.xlsx
Binary file not shown.
Binary file modified src/test/resources/unittesting/artifact/script/unitTest_json.xlsx
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/unittesting/artifact/script/unitTest_rdbms.xlsx
Binary file not shown.
Binary file not shown.
Binary file modified src/test/resources/unittesting/artifact/script/unitTest_web.xlsx
Binary file not shown.
Binary file modified src/test/resources/unittesting/artifact/script/unitTest_xml.xlsx
Binary file not shown.

0 comments on commit 5b599e4

Please sign in to comment.