Groovy ConfigSlurper is only partially building the structure when parsed - groovy

I am building a library for myself and using ConfigSlurper to parse a config.groovy file, and other config sources into an aggregated ConfigObject that I am using Micronaught compile time IOC to inject
#Bean
#Named('config')
ConfigObject config () {
Map envMap = [:]
def sysProps = System.getenv()
if (System.getProperty("env") ) {
envMap = System.getenv().findResult { it.key?.toLowerCase().contains "env" }.collect { [(it.key?.toLowerCase().substring(0, 2)): it.value.toLowerCase()] }
} else
envMap.get ('env', "development")
def env = envMap.get('env')
def resourcePath = "src${File.separatorChar}${env =="test" ?: "main"}${File.separatorChar}resources${File.separatorChar}"
ConfigObject config = new ConfigSlurper().parse(new File("${resourcePath}ApplicationConfig.groovy").text /*.toURI().toURL()*/)
config.put('systemProperties', sysProps)
config.putAll(envMap)
config.put('projectPath', System.getProperty("user.dir"))
config.put('resourcesPath', resourcePath.toString())
File resourceDirectory = new File ("${System.getProperty("user.dir")}$File.separatorChar$resourcePath")
FilenameFilter filter = {file, name -> name.matches(~/^.*properties$/) }
List propsFiles = resourceDirectory.listFiles (filter)
filter = {file, name -> name.matches(~/^.*yaml$/) }
List yamlFiles = resourceDirectory.listFiles (filter)
propsFiles.each {file ->
Properties prop = new Properties()
prop.load(new FileInputStream (file) )
Map propsMap = [:]
for (key in prop.stringPropertyNames()) {
propsMap.put(key, prop.getProperty(key))
}
config.putAll(propsMap)
}
yamlFiles.each {file ->
def yamlConfig = new YamlSlurper().parseText(file.text)
config.putAll(yamlConfig)
}
config
}
the resources/applicationConfig.groovy file looks like this
framework {
environments {
development {
server = "local" // choice of {local|clustered}
vertxOptions {
}
}
test {
server = "local" // choice of {local|clustered}
vertxOptions {
}
}
production {
server = "clustered" // choice of {local|clustered}
vertxOptions {
}
}
}
}
The code parses the groovy file but when I look at what in the configObject, its created the tope level framework map entry - but the lower levels are not there.
calling configObject.framework in code returns the object but it is of size() == 0!
I don't understand why the rest of the structure is not getting built.
Can any one advise why the internal structure is not being parsed and built.
Can anyone suggest what I have done wrong ?
I have just done a stripped back version like this
def configText = """
framework {
environments {
development {
server = "local" // choice of {local|clustered}
vertxOptions {
}
}
test {
server = "local" // choice of {local|clustered}
vertxOptions {
}
}
production {
server = "clustered" // choice of {local|clustered}
vertxOptions {
}
}
}
}
"""
ConfigObject conf = new ConfigSlurper().parse(configText)
assert conf.framework?.environments?.size() == 3
where the error shows this
Caught: Assertion failed:
assert conf.framework?.environments?.size() == 3
| | | | |
| | [:] 0 false
| ['environments':[:]]
['framework':['environments':[:]]]
Assertion failed:
assert conf.framework?.environments?.size() == 3
| | | | |
| | [:] 0 false
| ['environments':[:]]
['framework':['environments':[:]]]
at scripts.configSlurperTest.run(configSlurperTest.groovy:33)
at

The environments section when parsed by configslurper is conditional to the environment you pass to the constructor of ConfigSlurper itself.
Lets say you want the development config to be the default, but then you want to override this when run in test or production, you would do the following:
def configText = """
framework {
// defaults
mode = 'development'
server = "local" // choice of {local|clustered}
vertxOptions {
}
environments {
// test settings
test {
mode = 'testing'
server = "local" // choice of {local|clustered}
vertxOptions {
}
}
// production settings
production {
mode = 'live!'
server = "clustered" // choice of {local|clustered}
vertxOptions {
}
}
}
}
"""
// Default values
assert new ConfigSlurper().parse(configText).framework.mode == 'development'
// Test values
assert new ConfigSlurper('test').parse(configText).framework.mode == 'testing'
// Production values
assert new ConfigSlurper('production').parse(configText).framework.mode == 'live!'

Related

GEB: set default environment?

I declared a few environments in GebConfig via environments {} closure.
And if I specify -Dgeb.env in command line, everything works perfectly.
But when I start a single test for debugging purposes, I don't want to explicitly pass any additional environment variables.
What is the way to define the environment to be used by GEB as a default?
Just define a default driver outside of the environments My Geb config looks like this:
driver = {
new HtmlUnitDriver(true)
}
environments {
html_unit {
driver = {
new HtmlUnitDriver(true)
}
}
phantomjs {
driver = {
WebDriverManager.phantomjs().version("2.1.1").setup()
def pjsDriver = new PhantomJSDriver()
pjsDriver.manage().window().size = new Dimension(1024, 768)
pjsDriver
}
}
chrome {
driver = {
// (...)
}
}
chrome_headless {
System.setProperty("webdriver.chrome.logfile", "chromedriver.log")
System.setProperty("webdriver.chrome.verboseLogging", "true")
driver = {
// (...)
}
}
firefox {
driver = {
// (...)
}
}
ie {
driver = {
// (...)
}
}
edge {
driver = {
// (...)
}
}
opera {
driver = {
// (...)
}
}
win_app {
driver = {
// (...)
}
}
}

Find parent folder with file in node js

I want to be able to find target.json by passing a path to startingPoint.txt to a function in Node JS, given the following folder structure:
- root
- sub1
- sub2
startingPoint.txt
target.json
I've found a node package for it called find-up, but this is the kind of thing that probably takes no more than 6 lines of code.
export function findUp(start: Path, target: string): Path | undefined {
// this code here
}
const pathToTarget = findUp("root/sub1/sub2/startingPoint.txt", "target.json"/);
console.log(pathToTarget); // "root/target.json"
Old question I know, but I've just had to implement something similar and spent ages trying to find a good solution.
So, for future explorers, heres my solution using path and fs modules.
const PATH = require('path');
const FS = require('fs');
function findInPathAncestry(path, target) {
let current_directory_path = PATH.normalize(path).split(PATH.sep);
let result = false;
while(current_directory_path.length && !result) {
let current_path = current_directory_path.join(PATH.sep)+PATH.sep+target;
if(FS.existsSync(current_path)) {
result = current_path;
}
current_directory_path.pop();
}
return result;
}
usage example:
// example file structure:
// C:\
// | - path\
// | --- to\
// | ----- start\
// | ------- at\
// | ----- findme.txt
let start_location = "C:\path\to\start\at";
let target_file = "findme.txt";
console.log(findInPathAncestry(start_location, target_file));
// expected output:
// C:\path\to\findme.txt
// or false if file doesn't exist in path
the use of PATH.normalize and PATH.sep allows this to work in windows and unix environments
https://nodejs.org/api/path.html#path_path_sep
Here's what I have for now:
import { join, dirname } from 'path';
import { existsSync } from 'fs';
export let MAX_BACK_STEPS = 50;
export function findUp(start: string, target: string, boundary?: {
path: string,
inclusive: boolean,
}): string | null {
let currentDir = dirname(start);
let lastTry = false;
let backSteps = 0;
while (backSteps++) {
if (backSteps >= MAX_BACK_STEPS) {
console.error("Too many back steps");
return null;
}
if (boundary && boundary.path.includes(currentDir)) {
if (boundary.inclusive && lastTry === false) {
lastTry = true;
} else {
return null;
}
}
const targetTestPath = join(currentDir, target);
if (existsSync(targetTestPath)) {
return targetTestPath;
}
currentDir = join(currentDir, "../");
}
}

Serenity Cucumber4 Environment URL on Page Objects not working

Why does this not work?
When I run the feature file as "Run as Cucumber Feature" I get an error
java.lang.AssertionError: Undefined default URL for page object PageObject
Page Object
#DefaultUrl("page:register.page")
public class AccountCreationPage extends PageObject {
...
}
Config File (serenity.config)
environments {
local {
webdriver.base.url = "https://localhost"
}
demo {
webdriver.base.url = "https://demo.example.com"
}
prod {
webdriver.base.url = "https://example.com"
}
all {
home.page = "#{webdriver.base.url}"
register.page = "#{webdriver.base.url}/register"
}
}
https://johnfergusonsmart.com/environment-specific-configuration-in-serenity-bdd/
shows the use of #DefaultUrl("page:register.page")
Serenity-Cucumber4. Java. Eclipse.
the config file is required to have default as an entry 🤦
environments {
default {
webdriver.base.url = "https://localhost"
}
...
all {
home.page = "#{webdriver.base.url}"
register.page = "#{webdriver.base.url}/register"
}
}

Passing different parameters to created jobs

On Jenkins, using Job DLS plugin, I'm trying to prepare one script which will create jobs configured for different environments (dev and preprod). Depends on for which environments this job has to run, different parameters are needed.
In this situation how to define, in the shortest way, that parameters for dev environment include the same as preprod parameters plus additionally i.e. 2 more?
An example of the code which I use is presented below.
def environments = ["DEV", "PREPROD"]
def names = ["name1", "name2", "name3"]
def jobParameters = {
string {
name("browser")
defaultValue("CHROME")
description("Browser on which one tests will run")
trim(true)
}
string {
name("parameter1")
defaultValue("")
description("")
trim(true)
}
}
def jobParametersDev = {
jobParameters
string {
name("parameter2")
defaultValue("")
description("")
trim(true)
}
string {
name("parameter3")
defaultValue("")
description("")
trim(true)
}
}
def createJob(name, env, runCommand, jobParameters) {
job("Job-${-> name}-${-> env}") {
description("My first job for ${-> name}")
parameters(jobParameters)
steps {
shell {
command(runCommand)
}
}
}
}
for (name in names) {
for (env in environments) {
if (env == 'DEV') {
def runCommand = "python35 -u ./TestSuite-${-> name}.py %parameter1% %parameter2%,%parameter3% %browser%"
createJob(name, env, runCommand, jobParametersDev)
} else {
def runCommand = "python35 -u ./TestSuite-${-> name}.py %parameter1% ${-> env} %browser%"
createJob(name, env, runCommand, jobParameters)
}
}
}
To summarise - the last thing which I tried is:
def jobParametersDev = {
jobParameters
...
}
But it doesn't work... Only values for jobParametersDev are visible.
How to add these values? If it's not necessary I don't want to double the same code.
I will be really grateful for any help.
You can not simply call one closure within another. But you can chain method calls. You just need to pass the job reference.
def generateParameters = { job ->
job.parameters {
stringParam('param3', '', '')
// more params here...
}
}
def generateDevParameters = { job ->
generateParameters(job)
job.parameters {
stringParam('param4', '', '')
// more params here...
}
}
def createJob(name, generateParameters) {
def j = job(name) {
// more config here...
}
generateParameters(j)
}
createJob('test1', generateParameters)
createJob('test2', generateDevParameters)

groovy print environments from groovy.config

how do I print available environments from a config file? What is the form of the ojbect ConfigSlurper creates?
I tried
def config2 = new ConfigSlurper().parse(new File('obieeadmincfg.groovy').toURL())
config2.config.environments.each { println ${it} }
and
println prettyPrint(toJson(config2))
and
for ( i in 0 ..config2.config.environments.size()-1)
println config2.config.environments[i]
groovy.config
//Config3.groovy
obieadmin {
//default values
serverurl = "http://default.mycompany.com"
}
environments {
pldev01 {
obieeadmin {
serverurl = 'devgdwobi03.x.com'
}
}
plsbx02 {
obieeadmin {
serverurl = 'devgdwobi03.x.com'
}
}
}
I'm afraid you can't do this out-of box.
But using a bit of Groovy Metaprogramming it's achievable.
Groovy config slurper parses proper Groovy file, and you can do the same with GroovyShell. You can catch call to environment method providing closure in binding. In that closure you have to collect all top-level method calls(with same methodMissing).
Providing base script with property and method missing handlers, you can suppress runtime errors, and execute script without much care to other properties.
Not the nicest path, but it works.
package test
import org.codehaus.groovy.control.CompilerConfiguration
class A extends Script {
def propertyMissing(String name) { null }
def propertyMissing(String name, def arg) {}
def methodMissing(String name, def args) {}
#Override Object run() { null }
}
class NameCollector {
def envs = []
def methodMissing(String name, def args) { envs << name }
}
// configure interceptors.
def configuration = new CompilerConfiguration()
configuration.scriptBaseClass = 'test.A'
def nc = new NameCollector()
def environments = { Closure it ->
it.delegate = nc;
it.resolveStrategy = Closure.DELEGATE_ONLY
it()
}
// execute config script
new GroovyShell([environments: environments] as Binding, configuration).evaluate(new File("config.groovy"))
nc.envs // Return, print them.
Not sure if this is going to work forever but currently you can do it by overriding the setting for the 'environments' conditional block, like this:
def config = """environments {
dev {
foo = "bar"
}
prod {
foo = "baz"
}
}"""
def configSlurper = new ConfigSlurper()
configSlurper.registerConditionalBlock('environments', null)
assert configSlurper.parse(config).environments.keySet() == ['dev', 'prod'].toSet()

Resources