I have a groovy file created under "vars" in a Jenkins-shared-lib.
Few variables are defined inside call().
I want to mock the variables in a groovy test file.
Variables are defined in sonarGradleProject.groovy in vars:
#!/usr/bin/env groovy
import static com.example.jenkins.Constants.*
def call(Map params = [:]) {
def sonarCredId = params.sonarCredentialId ?: SONAR_CREDENTIAL_ID_DEFAULT;
def sonarUrl = params.sonarUrl ?: SONAR_URL;
def profile = params.profile ?: 'example';
withCredentials([string(credentialsId: sonarCredId, variable: 'SONAR_TOKEN')]) {
sh "./gradlew --no-daemon jacocoTestReport sonarqube -P${profile} -Dsonar.host.url=${sonarUrl} -Dsonar.login=$SONAR_TOKEN -Dsonar.verbose=true"
}
}
Test file looks like this:
import com.example.jenkins.testing.JenkinsPipelineSpecification
class SonarGradleProjectSpec extends JenkinsPipelineSpecification {
def "expected minor value"() {
setup:
def sonarGradleProject = loadPipelineScriptForTest("vars/sonarGradleProject.groovy")
explicitlyMockPipelineStep('withCredentials')
when:
def verType = sonarGradleProject(profile: 'foo')
then:
1 * getPipelineMock("sh")("./gradlew --no-daemon jacocoTestReport sonarqube -P${profile} -Dsonar.host.url=${sonarUrl} -Dsonar.login=$SONAR_TOKEN -Dsonar.verbose=true")
expect:
'foo' == profile
}
}
On executing the test case, I get this error:
java.lang.IllegalStateException: There is no pipeline variable mock for [profile].
1. Is the name correct?
2. Is it a GlobalVariable extension point? If so, does the getName() method return [profile]?
3. Is that variable normally defined by Jenkins? If so, you may need to define it by hand in your Spec.
4. Does that variable come from a plugin? If so, is that plugin listed as a dependency in your pom.xml?
5. If not, you may need to call explicitlyMockPipelineVariable("profile") during your test setup.
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at com.example.jenkins.testing.JenkinsPipelineSpecification.addPipelineMocksToObjects_closure1$_closure15(JenkinsPipelineSpecification.groovy:755)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at SonarGradleProjectSpec.expected minor value(sonarGradleProjectSpec.groovy:12)
Caused by: groovy.lang.MissingPropertyException: No such property: (intercepted on instance [SonarGradleProjectSpec#3dfb1626] during test [SonarGradleProjectSpec#3dfb1626]) profile for class: SonarGradleProjectSpec
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at com.example.jenkins.testing.JenkinsPipelineSpecification.addPipelineMocksToObjects_closure1$_closure15(JenkinsPipelineSpecification.groovy:754)
... 3 more
I want to use the variables in test.
How do I mock them?
I've run into similar issues before. The issue for me is that I was trying to verify the interactions of a mock that uses string interpolation. So for the one entry in the then section...
1 * getPipelineMock("sh")("./gradlew --no-daemon jacocoTestReport sonarqube -P${profile} -Dsonar.host.url=${sonarUrl} -Dsonar.login=$SONAR_TOKEN -Dsonar.verbose=true")
I would define the profile and sonarUrl variables in the setup section (i.e., def profile = 'test123'), referenced to verify the mocked sh step interaction. I believe you would also want to adjust the assignment of default values to use the null-safe ?. operator to access key-value pairs in the params map. So def sonarUrl = params.sonarUrl ?: SONAR_URL; would become def sonarUrl = params?.sonarUrl ?: SONAR_URL;
You may have already addressed this and omitted it from the question, but I figured I'd share this information too. Please ignore the following information if you've already addressed it. :)
The three environment variables defined in the script - SONAR_CREDENTIAL_ID_DEFAULT, SONAR_URL, and SONAR_TOKEN - will need to be injected into the script before calling sonarGradleProject. That should be sufficient to test default values if key-value pairs are not provided in the params map.
setup:
def sonarGradleProject = loadPipelineScriptForTest("vars/sonarGradleProject.groovy")
explicitlyMockPipelineStep('withCredentials')
// Inject environment variables.
// First argument is the variable name and second argument is the variable value.
sonarGradleProject.getBinding().setVariable('SONAR_CREDENTIAL_ID_DEFAULT', 'test123')
sonarGradleProject.getBinding().setVariable('SONAR_URL', 'test123')
sonarGradleProject.getBinding().setVariable('SONAR_TOKEN', 'test123')
Related
I went through the following link and successfully implemented a task which calls build.gradle file from another project. i.e. solution provided by #karl worked for me.
But I need something up on that.
Can somebody help me to know how I can pass command line arguments while calling another build.gradle? Command line argument should be the variable which I have generated from my current build.gradle file.
In my case, I am defining a buildNumber and doing something like this:
def buildNumber = '10.0.0.1'
def projectToBuild = 'projectName'
def projectPath = "Path_till_my_Project_Dir"
task executeSubProj << {
def tempTask = tasks.create(name: "execute_$projectToBuild", type: GradleBuild)
// ****** I need to pass buildNumber as command line argument in "$projectPath/$projectToBuild/build.gradle" ******
tempTask.tasks = ['build']
tempTask.buildFile = "$projectPath/$projectToBuild/build.gradle"
tempTask.execute()
}
You should never call execute directly on any gradle object. The fact it's feasible doesn't mean it should be done and it's highly discouraged since you intrude internal gradle's execution graph structure.
What you need is a task of type GradleBuild which has StartParameter field that can be used to carry build options.
So:
task buildP2(type: GradleBuild) {
buildFile = '../p2/build.gradle'
startParameter.projectProperties = [lol: 'lol']
}
Full demo can be found here, navigate to p1 directory and run gradle buildP2.
You should modify your script in the following way:
def buildNumber = '10.0.0.1'
def projectToBuild = 'projectName'
def projectPath = "Path_till_my_Project_Dir"
task executeSubProj(type: GradleBuild) {
buildFile = "$projectPath/$projectToBuild/build.gradle"
tasks = ['build']
startParameter.projectProperties = [buildNumber: '10.0.0.1']
}
In the project that is executed use project.findProperty('buildNumber') to get the required value.
I have code that reads in a pom.xml file then attempts to re-serialize and write it back out:
// Get the file raw text
def pomXMLText = readFile(pomFile)
// Parse the pom.xml file
def project = new XmlSlurper(false, false).parseText(pomXMLText)
... do some useful stuff ...
def pomFileOut = "$WORKSPACE/pomtest.xml"
def pomXMLTextOut = groovy.xml.XmlUtil.serialize(project)
println "pomXMLTextOut = $pomXMLTextOut" // <-- This line prints to updated XML
writeFile file: pomFileOut, text: pomXMLTextOut // <-- This line crashes with the error listed in the posting title: java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild
I've tried casting the pomXMLTextOut variable to a String. I tried applying the .text() method, which gets a jenkins sandbox security error. Has anyone else been able to successfully write an XML file from a groovy script running in a Jenkins pipeline?
BTW, I've also tried using a File object, but that isn't remotable across jenkins nodes. It works as long as the job always runs on master.
You could try a #NonCPS annotation and close those non-serializable objects in a funcation like this
#NonCPS
def writeToFile(String text) {
...
}
Here's the explanation from Pipeline groovy plugin
#NonCPS methods may safely use non-Serializable objects as local
variables
I'm trying to configure the following custom task:
task antecedeRelease(type: AntecedeReleaseTask) {
antecedeWithVersion = project.'antecede-with-version'
antecedeToVersion = project.'antecede-to-version'
}
The problem is that the properties antecede-with-version and antecede-to-version are to be set through the command line with a -P option. If they're not set and antecedeRelease isn't being called, that shouldn't be a cause for an error:
$ ./gradlew tasks
org.gradle.api.GradleScriptException: A problem occurred evaluating project ...
Caused by: groovy.lang.MissingPropertyException: Could not find property 'antecede-with-version' on project ...
I could conditionally define the antecedeRelease task such that it's defined only if those properties are defined but I'd like to keep the build.gradle file as clean as possible.
If you need the antecedeRelease task to run "lazily" as-in, at the end of the configuration phase, or at the beginning of the execution phase, your best bet is to use doFirst
task antecedeRelease(type: AntecedeReleaseTask) {
doFirst {
antecedeWithVersion = project.'antecede-with-version'
antecedeToVersion = project.'antecede-to-version'
}
}
One option might be to use Groovy's elvis operator like so:
task antecedeRelease(type: AntecedeReleaseTask) {
antecedeWithVersion = project.ext.get('antecede-with-version') ?: 'unused'
antecedeToVersion = project.ext.get('antecede-with-version') ?: 'unused'
}
If this fails still, you can consider project.ext.has('property') when setting the value.
I am currently modularizing our gradle build in order to have a libs/commons.gradle file containing a lot of global stuff. I need this because of various branches of the software beeing developed in parallel and we'd like to avoid to spread every scriptfile change among all branches.
So I created that lib file and used "apply from" to load it :
apply from: 'gradle/glib/commons.gradle'
Inside commons.gradle I define the svnrevision function :
...
def svnRevision = {
ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
SVNClientManager clientManager = SVNClientManager.newInstance(options);
SVNStatusClient statusClient = clientManager.getStatusClient();
SVNStatus status = statusClient.doStatus(projectDir, false);
SVNRevision revision = status.getCommittedRevision();
return revision.getNumber().toString();
}
...
I am calling the function from my including build.gradle:
...
task writeVersionProperties {
File f = new File(project.webAppDirName+'/WEB-INF/version.properties');
if (f.exists()) { f.delete(); }
f = new File(project.webAppDirName+'/WEB-INF/version.properties');
FileOutputStream os = new FileOutputStream(f);
os.write(("version="+svnRevision()).getBytes());
os.flush();
os.close();
}
...
But I end up in :
...
FAILURE: Build failed with an exception.
* Where:
Build $PATH_TO/build20.gradle
* What went wrong:
A problem occurred evaluating root project 'DEV_7.X.X_GRADLEZATION'.
> Could not find method svnRevision() for arguments [] on root project 'DEV_7.X.X_GRADLEZATION'.
...
So my queston is : How can I call a subfunction in gradle, which is defined in an included script?
Any help appreciated!
From http://www.gradle.org/docs/current/userguide/writing_build_scripts.html:
13.4.1. Local variables
Local variables are declared with the def keyword. They are only
visible in the scope where they have been declared. Local variables
are a feature of the underlying Groovy language.
13.4.2. Extra properties
All enhanced objects in Gradle's domain model can hold extra
user-defined properties. This includes, but is not limited to,
projects, tasks, and source sets. Extra properties can be added, read
and set via the owning object's ext property. Alternatively, an ext
block can be used to add multiple properties at once.
If you declare it as:
ext.svnRevision = {
...
}
and don't change the call, I expect it will work.
Environment : Rails 3.1.1 and Rspec 2.10.1
I am loading all my application configuration through an external YAML file. My initializer (config/initializers/load_config.rb) looks like this
AppConfig = YAML.load_file("#{RAILS_ROOT}/config/config.yml")[RAILS_ENV]
And my YAML file sits under config/config.yml
development:
client_system: SWN
b2c_agent_number: '10500'
advocacy_agent_number: 16202
motorcycle_agent_number: '10400'
tso_agent_number: '39160'
feesecure_eligible_months_for_monthly_payments: 1..12
test:
client_system: SWN
b2c_agent_number: '10500'
advocacy_agent_number: 16202
motorcycle_agent_number: '10400'
tso_agent_number: '39160'
feesecure_eligible_months_for_monthly_payments: 1..11
And I access these values as, For example AppConfig['feesecure_eligible_months_for_monthly_payments']
In one of my tests I need AppConfig['feesecure_eligible_months_for_monthly_payments'] to return a different value but am not sure how to accomplish this. I tried the following approach with no luck
describe 'monthly_option_available?' do
before :each do
#policy = FeeSecure::Policy.new
#settlement_breakdown = SettlementBreakdown.new
#policy.stub(:settlement_breakdown).and_return(#settlement_breakdown)
#date = Date.today
Date.should_receive(:today).and_return(#date)
#config = mock(AppConfig)
AppConfig.stub(:feesecure_eligible_months_for_monthly_payments).and_return('1..7')
end
.....
end
In my respective class I am doing something like this
class Policy
def eligible_month?
eval(AppConfig['feesecure_eligible_months_for_monthly_payments']).include?(Date.today.month)
end
....
end
Can someone please point me in the right direction!!
The method that is being called when you do AppConfig['foo'] is the [] method, which takes one argument (the key to retrieve)
Therefore what you could do in your test is
AppConfig.stub(:[]).and_return('1..11')
You can use with to setup different expectations based on the value of the argument, ie
AppConfig.stub(:[]).with('foo').and_return('1..11')
AppConfig.stub(:[]).with('bar').and_return(3)
You don't need to setup a mock AppConfig object - you can stick your stub straight on the 'real' one.