Skip to content

Instantly share code, notes, and snippets.

@otarsko
Last active January 2, 2019 12:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save otarsko/a1ce2f07dbb95b3dd3d813f5b1f72f28 to your computer and use it in GitHub Desktop.
Save otarsko/a1ce2f07dbb95b3dd3d813f5b1f72f28 to your computer and use it in GitHub Desktop.
Groovy breaklet to test how many OSGi configs are needed to make browser timeout on Configuration Manager page load
import groovy.transform.InheritConstructors
import org.apache.sling.installer.api.jmx.InstallerMBean
//-------------------------------------
// *** Max OSGi configs
//-------------------------------------
// Breaklet Config
//-------------------------------------
def breakForDurationInSec = 120
def maxIterations = 100
def configurationsPath = "/apps/weretail/config"
def configurationsPerIteration = 1000
def sessionUser = 'admin'
def sessionPassword = 'admin'
def cleanUp = {
def configsRoot = resourceResolver.getResource(configurationsPath)
configsRoot.listChildren().each { configResource ->
def configName = configResource.getName()
if (configName.matches("testConfig_iteration_.*")) {
resourceResolver.delete(configResource)
}
}
resourceResolver.commit()
}
def createConfiguration = { resourceResolver, parentResource, configName ->
resourceResolver.create(parentResource, configName, ["jcr:primaryType": "sling:OsgiConfig", "test.property":"test/value1"])
}
def getResourceResolverForSession = { session ->
return getService(ResourceResolverFactory.class).getResourceResolver(Collections.singletonMap("user.jcr.session", (Object) session))
}
//-------------------------------------
// Breaklet
//-------------------------------------
new MyBreaklet(this, breakForDurationInSec, sessionUser, sessionPassword, maxIterations).run ({ breaklet, currentIt, itSession ->
def itResourceResolver = getResourceResolverForSession(session)
def configsRoot = itResourceResolver.getResource(configurationsPath)
def lastAddedConfigurationName
for(int resourceNum = 1; resourceNum <= configurationsPerIteration; resourceNum++) {
def newConfigName = "testConfig_iteration_${currentIt}_${resourceNum}"
createConfiguration(itResourceResolver, configsRoot, newConfigName)
lastAddedConfigurationName = "testConfig_iteration_${currentIt}_${resourceNum}"
}
itResourceResolver.commit()
breaklet.logOpFinished("Created and saved $configurationsPerIteration configuration nodes", "")
breaklet.waitForConfigsInstalled(breaklet.getInstalledResourceCount() + configurationsPerIteration, breakForDurationInSec)
def browserDurationInMillis = breaklet.runInBrowser("""
page.on('domcontentloaded', () => {
let delta = new Date().getTime()-startMillis
resultProps['domContentLoaded'] = delta
console.log("DOM Content loaded after " + delta);
});
console.log("Testing with Web Console Configuration")
await page.goto(baseUrl+'/system/console/configMgr', { waitUntil: "networkidle2", timeout: ${breakForDurationInSec * 1000} });
if (!resultProps['domContentLoaded']) {
throw new Error("Smth went wrong here!")
}
let configModalStart = new Date().getTime();
let lastConfiguration = await waitForXPath(page, "//td[text() = '${lastAddedConfigurationName}']")
lastConfiguration.click();
await waitForXPath(page, "//span[text() = '${lastAddedConfigurationName}']")
resultProps['configOpened'] = new Date().getTime() - configModalStart
""")
def browserResultProps = breaklet.browser.resultProps
def timeToLoadDom = browserResultProps.getProperty('domContentLoaded')
def timeToLoadConfigModal = browserResultProps.getProperty('configOpened')
breaklet.logOpFinished("DOM Content loaded", "", timeToLoadDom)
breaklet.logOpFinished("Configuration modal is open", "", timeToLoadConfigModal)
}, {
cleanUp()
})
@InheritConstructors
class MyBreaklet extends Breaklet {
def installerMBean
def getInstalledResourceCount = {
if (!installerMBean) {
installerMBean = script.getService(InstallerMBean.class)
}
return installerMBean.getInstalledResourceCount()
}
def waitForConfigsInstalled = { long expectedInstalledCount, long maxWaitInSec = 300 ->
boolean timeout = false
def installedCurrently
while((installedCurrently = getInstalledResourceCount()) < expectedInstalledCount) {
if((lastTimestamp + maxWaitInSec*1000) < System.currentTimeMillis()) {
timeout = true
break
}
Thread.sleep(50)
}
logOpFinished("Wait for configs installed", ( timeout ? 'Timed out after $maxWaitInSec sec ' : '') + "expectedInstalledCount: $expectedInstalledCount | installedCurrently: $installedCurrently")
}
def run(toRun, toRunAfterAll) {
try {
super.run(toRun)
} finally {
toRunAfterAll()
}
}
}
// DO NOT CHANGE BEYOND THIS POINT - master is https://gist.github.com/ghenzler/58ce57794ba9b5a60f2a2e60160be747 here
// --------------------------------------------------------------------
// Breaklet Base Utilities (to be copied at bottom of other breaklets)
// --------------------------------------------------------------------
import org.apache.jackrabbit.commons.JcrUtils
import org.apache.commons.lang3.StringUtils
import java.util.concurrent.ThreadLocalRandom
import javax.jcr.query.*
import org.apache.sling.jcr.api.*
import org.apache.sling.api.resource.observation.*
import java.util.Properties
import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets
import org.osgi.service.event.EventConstants
import org.osgi.service.event.EventHandler
import org.osgi.service.event.Event
import org.apache.commons.io.IOUtils
import org.apache.sling.hc.util.FormattingResultLog
class BreakletBrokeException extends RuntimeException {
String opName
String timeSpent
BreakletBrokeException(opName, timeSpent) {
this.opName = opName
this.timeSpent = timeSpent
}
}
class BreakletPanicException extends RuntimeException { }
class Breaklet {
int iterations
int breakForDurationInSec
String user
String password
long lastTimestamp = System.currentTimeMillis()
def script
long currentIteration
String currentItLogMarker
Map<String,List> timings = new LinkedHashMap<String,List>()
Browser browser
def resourceChangeListener = null
def resourceChangeListenerReg = null
def resourceChangeListenerPath = null
def resourceChangeEventHandler = null
def resourceChangeEventHandlerReg = null
def resourceChangeEventHandlerPath = null
Breaklet(def script, int breakForDurationInSec, String user=null, String password=null, int iterations=Integer.MAX_VALUE) {
this.script = script
this.breakForDurationInSec = breakForDurationInSec
this.user = user
this.password = password
this.iterations = iterations
this.browser = new Browser(script, breakForDurationInSec, user, password)
}
def logOpFinished(String opName, String opDescription="", def timeSpentMillis=null, def neverBreak=false) {
script.logMsg(StringUtils.leftPad(currentItLogMarker, 7)+" " + StringUtils.rightPad(getTimeSpent(opName, timeSpentMillis as Long, neverBreak) + ":", 8) + " "+opName + " "+opDescription)
currentItLogMarker = ""
}
def getTimeSpent(String opName, Long timeSpentMillis=null, def neverBreak=false) {
long delta = timeSpentMillis ?: System.currentTimeMillis()-lastTimestamp
def timingSeriesForOp = timings.get(opName)
if(timingSeriesForOp==null) { timings.put(opName, (timingSeriesForOp = new ArrayList<Long>())) }
while(timingSeriesForOp.size() < currentIteration-1) { timingSeriesForOp.add(timingSeriesForOp.size(), '') }
timingSeriesForOp.add((int)currentIteration-1, delta)
String timeSpent = FormattingResultLog.msHumanReadable(delta)
if((delta/1000) > breakForDurationInSec && !neverBreak) {
throw new BreakletBrokeException(opName, timeSpent)
}
lastTimestamp = System.currentTimeMillis()
return timeSpent
}
def run(toRun) {
File currentWorkingDir = new File(System.getProperty('user.dir'))
File panicFile = new File(currentWorkingDir, 'panic')
try {
(1..iterations).each { itNo ->
currentItLogMarker = itNo+":"
def sessionForIteration = null
try {
if(StringUtils.isNotBlank(user)) {
sessionForIteration = getSessionFor(user, password)
}
currentIteration = itNo
def sessionForIt = sessionForIteration ?: script.session
toRun(this, currentIteration, sessionForIt)
if(panicFile.exists()) {
panicFile.renameTo(new File(currentWorkingDir, 'no-panic'))
throw new BreakletPanicException()
}
} finally {
if(sessionForIteration!=null && sessionForIteration.isLive()) {
sessionForIteration.logout()
}
}
}
script.logMsg("\n\nBreaklet survived - "+iterations+" iterations finished without exceeding the threshold\n")
} catch(BreakletBrokeException e) {
script.logMsg("\n\nBreaklet broke at iteration "+currentIteration+": Operation '"+e.opName+"' "+e.timeSpent + " >= "+breakForDurationInSec + "sec\n")
} catch(BreakletPanicException e) {
script.logMsg("\nBreaklet broke due to panic after iteration "+currentIteration+"\n")
} catch(Exception e) {
script.logMsg("\n\nBreaklet broke at iteration "+currentIteration+" with unexpected exception: "+e)
script.log.info("Exception:"+e, e)
} finally {
String result = ""
timings.keySet().each { key ->
result += ""+key+","+ StringUtils.join(timings.get(key),',') + "\n"
}
new File(currentWorkingDir, 'breaklet-result-'+new Date().getTime()+'.csv').text = result
if(resourceChangeListenerReg) {
resourceChangeListenerReg.unregister()
}
if(resourceChangeEventHandlerReg) {
resourceChangeEventHandlerReg.unregister()
}
}
}
def runInBrowser(String puppeteerScript) {
return browser.run(puppeteerScript)
}
def getSessionFor(sessionUser, sessionPassword) {
def repository = script.session.getRepository()
def session = repository.login(new SimpleCredentials(sessionUser, sessionPassword.toCharArray()));
return session
}
def registerResourceChangeListener(paths) {
resourceChangeListenerPath = paths
resourceChangeListener = new ResourceChangeListener() {
int changesReceived = 0
int callsReceived = 0
def allChanges = []
public void onChange(List<ResourceChange> changes) {
changesReceived += changes.size()
callsReceived++
allChanges.addAll(changes)
}
public void reset() {
changesReceived = 0
callsReceived = 0
allChanges = []
}
}
resourceChangeListenerReg = script.bundleContext.registerService("org.apache.sling.api.resource.observation.ResourceChangeListener", resourceChangeListener, new Hashtable([(ResourceChangeListener.PATHS): paths]))
return resourceChangeListener
}
def registerResourceChangeEventHandler(path) {
resourceChangeEventHandlerPath = path
resourceChangeEventHandler = new EventHandler() {
int eventsReceived = 0
def allEvents = []
public void handleEvent(final Event event) {
eventsReceived++
allEvents.addAll(event)
}
public void reset() {
eventsReceived = 0
allEvents = []
}
}
resourceChangeEventHandlerReg = script.bundleContext.registerService("org.osgi.service.event.EventHandler", resourceChangeEventHandler,
new Hashtable([
(EventConstants.EVENT_TOPIC): "org/apache/sling/api/resource/Resource/*",
(EventConstants.EVENT_FILTER): "("+SlingConstants.PROPERTY_PATH+"="+path+"*)",
]))
return resourceChangeEventHandler
}
def waitForResourceChangedEvents(long minimumExpectedChanges, long maxWaitInSec=300, String opName=null) {
boolean timeout = false
int waitCounter = 0
while((resourceChangeListener && resourceChangeListener.changesReceived < minimumExpectedChanges)
|| (resourceChangeEventHandler && resourceChangeEventHandler.eventsReceived < minimumExpectedChanges)
) {
if((lastTimestamp + maxWaitInSec*1000) < System.currentTimeMillis()) {
timeout = true
break
}
Thread.sleep(50)
}
String resourceChangeListenerResult = resourceChangeListener ? " changesReceived="+resourceChangeListener.changesReceived+" (in "+resourceChangeListener.callsReceived +" calls) " : ""
String resourceChangeEventHandlerResult = resourceChangeEventHandler ? " eventsReceived="+resourceChangeEventHandler.eventsReceived : ""
opName = opName ?: "Wait for resource changed events ("+(resourceChangeListenerPath?:resourceChangeEventHandlerPath)+")"
logOpFinished(opName, (timeout?'Timed out after '+maxWaitInSec+'sec ':'') + resourceChangeListenerResult + resourceChangeEventHandlerResult)
resourceChangeListener?.reset()
resourceChangeEventHandler?.reset()
}
def performRequest(String requestPath, String reqUser=user, String reqPassword=password) {
def con = new URL(script.getBaseUrl()+requestPath).openConnection();
String encoded = Base64.getEncoder().encodeToString((reqUser+":"+reqPassword).getBytes(StandardCharsets.UTF_8));
con.setRequestProperty("Authorization", "Basic "+encoded);
def result = IOUtils.toString(con.getInputStream());
return result
}
}
class Browser {
// config
def script
String user
String password
int breakForDurationInSec
// state
File browserDir
String testJsTemplate
Properties resultProps
Browser(def script, int breakForDurationInSec, String user, String password) {
this.script = script
this.breakForDurationInSec = breakForDurationInSec
this.user = user
this.password = password
}
def init() {
browserDir = new File(System.getProperty('user.dir')+'/puppeteer')
if(!browserDir.exists()) {
script.logMsg("init "+ browserDir)
browserDir.mkdir()
new File(browserDir, 'package.json').text = """
{
"name": "puppeteer-breaklet",
"version": "1.0.0",
"scripts": {
"test": "node test.js"
},
"dependencies": {
"puppeteer": "1.7.0"
}
}
""".stripIndent()
def result = script.exec("npm install", browserDir)
script.logMsg("npm install result: "+ result)
}
testJsTemplate = '''
const puppeteer = require('puppeteer');
let baseUrl = process.argv[2]
let user = process.argv[3]
let password = process.argv[4]
let startTime = process.argv[5]
let defaultTimeout = 120*1000
let screenshotCreated = false
let loginPage = "/libs/granite/core/content/login.html"
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getCrxdeXPath(nodeName) {
return "//*[@id='repository']//a[span/text()='"+nodeName+"']"
}
async function waitForXPath(page, xpath) {
await page.waitForXPath(xpath, { timeout: defaultTimeout})
let nodeElements = await page.$x(xpath)
return nodeElements[0]
}
async function waitForCrxdeNodeWithName(page, nodeName) {
let nodeElXPath = getCrxdeXPath(nodeName)
return await waitForXPath(page, nodeElXPath)
}
async function waitForAndClick(page, selector, retry=true) {
await page.waitForSelector(selector, { timeout: defaultTimeout, visible: true})
if (await page.$(selector) === null) {
throw new Error("Could not find " + selector + " even after waiting for " + defaultTimeout);
}
try {
await page.click(selector)
} catch(err) {
if(retry) {
console.log("Error " +err+" while clicking on selector "+selector+", retrying...")
await sleep(200)
waitForAndClick(page, selector, false);
} else {
throw err
}
}
}
async function clickOnIconOfCrxdeNode(page, nodeHandle) {
await page.evaluate(node => node.previousSibling.previousSibling.click(), nodeHandle);
}
async function createScreenshot(page, name=null) {
if(page) {
await page.screenshot({path: 'run-'+startMillis+(name ? '-'+name : '')+'.png'});
screenshotCreated = true
}
}
async function run() {
var resultProps = {};
const browser = await puppeteer.launch({headless: true, dumpio: true, args: ['--window-size=1440,900', "--no-sandbox"]});
let page = null
try {
//const context = await browser.createIncognitoBrowserContext();
page = await browser.newPage(); // context or browser
await page.setViewport({width: 1440, height: 900});
await page.goto(baseUrl + loginPage);
await page.$eval('#username', (el, pUsername) => el.value = pUsername, user);
await page.$eval('#password', (el, pPassword) => el.value = pPassword, password);
await sleep(500);
await page.$eval('#login', form => form.submit());
await page.waitForNavigation({ waitUntil: 'networkidle0'})
if(new RegExp('.*?'+loginPage+'.*').test(await page.url())) {
throw new Error("Could not login!");
} else {
console.log("Logged in user "+user)
}
startMillis = new Date().getTime()
console.log("Starting user script at start milliseconds =", startMillis)
%SCRIPT%
} catch(err) {
console.log("Error during execution: ", err)
} finally {
if(!screenshotCreated) {
await createScreenshot(page)
}
browser.close();
let delta = new Date().getTime()-startMillis
console.log("----------------")
for (let key in resultProps) {
console.log(key+"="+resultProps[key])
}
console.log("browserDurationInMillis="+delta)
}
}
run();
'''.stripIndent()
}
def run(String scriptToRun) {
init()
new File(browserDir, 'test.js').text = testJsTemplate.replace('%SCRIPT%', scriptToRun)
def baseUrl = script.getBaseUrl()
def startTime = System.currentTimeMillis()
def cmd = "npm test $baseUrl $user $password $startTime"
def scriptResult = script.exec(cmd, browserDir, breakForDurationInSec)
new File(browserDir, 'run-'+new Date().getTime()+".log").text = scriptResult
if(scriptResult.contains("Error during execution:")) {
throw new IllegalStateException("Browser execution failed")
}
def propsString = StringUtils.substringAfter(scriptResult, "----------------\n")
resultProps = new Properties()
resultProps.load(new ByteArrayInputStream(propsString.getBytes(StandardCharsets.UTF_8)))
long browserDurationInMillis = resultProps.containsKey('breakForDurationInSec') ? Long.parseLong(resultProps.getProperty('breakForDurationInSec')) : (System.currentTimeMillis()-startTime)
return browserDurationInMillis
}
}
def logMsg(String msg) {
log.info(msg)
println(msg)
}
def getBaseUrl() {
def aemPort = System.getProperty('sun.java.command').replaceFirst(".*?-p ([0-9]+) .*", "\$1")
return "http://localhost:"+aemPort
}
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import org.apache.commons.exec.ExecuteWatchdog
def exec(String command, File dir, int breakForDurationInSec=600) {
CommandLine cmdLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory(dir)
ExecuteWatchdog watchdog = new ExecuteWatchdog(breakForDurationInSec * 1000);
executor.setWatchdog(watchdog);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
executor.setExitValues([0,1,143] as int[])
int exitValue = executor.execute(cmdLine);
String scriptOutput = outputStream.toString()
if(exitValue == 1) {
logMsg("Script '"+command+"' returned exit code 1, output:\n "+ scriptOutput)
}
return scriptOutput
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment