Skip to content

Commit

Permalink
ftest: Refactor fcli invocation mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
rsenden committed Jul 14, 2023
1 parent 0d4ae0a commit 390eb9b
Show file tree
Hide file tree
Showing 15 changed files with 251 additions and 161 deletions.
1 change: 0 additions & 1 deletion fcli-other/fcli-functional-test/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ testing {
implementation 'org.apache.groovy:groovy'
implementation platform("org.spockframework:spock-bom:2.3-groovy-4.0")
implementation "org.spockframework:spock-core"
implementation 'io.github.joke:spock-outputcapture:3.0.1'
implementation 'org.junit.platform:junit-platform-launcher:1.9.3'
if ( !project.hasProperty('ftest.fcli') || project.property('ftest.fcli')=='build' ) {
implementation project("${fcliAppRef}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package com.fortify.cli.ftest._common.runner;
package com.fortify.cli.ftest._common;

import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path

import com.fortify.cli.ftest._common.Input
import org.spockframework.runtime.IStandardStreamsListener
import org.spockframework.runtime.StandardStreamsCapturer

import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.TupleConstructor

@CompileStatic
public class FcliRunner {
public class Fcli {
private static Path fcliDataDir;
private static IRunner runner

Expand All @@ -22,15 +23,36 @@ public class FcliRunner {
runner = createRunner()
}

static boolean run(String[] args) {
/**
* This method allows for running fcli with the given arguments,
* returning execution results in an FcliResult object. The
* returned object can be used by feature specs to define
* expectations on properties like 'success' status, exit code,
* and stdout/stderr contents.
* @param args Arguments to pass to fcli
* @return FcliResult describing fcli execution result
*/
static FcliResult run(String[] args) {
if ( !runner ) {
throw new IllegalStateException("Runner not initialized")
}
return runner.run(args)==0
new FcliOutputCapturer().start().withCloseable {
int exitCode = runner.run(args)
return new FcliResult(exitCode, it.stdout, it.stderr)
}
}

/**
* This method allows for running fcli with the given arguments,
* throwing an exception with the given message if the fcli
* invocation was not successful. This method shouldn't be used
* by feature specs; these should call the run() method and
* define proper expectations like expected success.
* @param msg Exception message to use in case of failure
* @param args Arguments to pass to fcli
*/
static void runOrFail(String msg, String[] args) {
if ( !run(args) ) {
if ( !run(args).success ) {
throw new IllegalStateException(msg)
}
}
Expand Down Expand Up @@ -67,6 +89,16 @@ public class FcliRunner {
}
}

@Immutable
static class FcliResult {
int exitCode;
List<String> stdout;
List<String> stderr;
final boolean isSuccess() {
exitCode==0
}
}

private static interface IRunner extends AutoCloseable {
int run(String... args);
}
Expand Down Expand Up @@ -114,4 +146,48 @@ public class FcliRunner {
}
}
}

private static class FcliOutputCapturer implements IStandardStreamsListener, Closeable, AutoCloseable {
@Lazy private static final StandardStreamsCapturer capturer = createCapturer();
private StringBuffer stdoutBuffer = null;
private StringBuffer stderrBuffer = null;

@Override
void standardOut(String message) {
stdoutBuffer.append(message)
}

@Override
void standardErr(String message) {
stderrBuffer.append(message)
}

List<String> getStdout() {
return getLines(stdoutBuffer)
}

List<String> getStderr() {
return getLines(stderrBuffer)
}

private List<String> getLines(StringBuffer sb) {
return sb.toString().split("(\\r\\n|\\n|\\r)").toList()
}

private static final StandardStreamsCapturer createCapturer() {
def capturer = new StandardStreamsCapturer()
capturer.start()
return capturer
}

FcliOutputCapturer start() {
this.stdoutBuffer = new StringBuffer()
this.stderrBuffer = new StringBuffer()
capturer.addStandardStreamsListener(this);
return this;
}
void close() {
capturer.removeStandardStreamsListener(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.fortify.cli.ftest._common.extension;

import org.spockframework.runtime.extension.IGlobalExtension
import org.spockframework.runtime.model.FieldInfo
import org.spockframework.runtime.model.SpecInfo

import com.fortify.cli.ftest._common.Fcli
import com.fortify.cli.ftest._common.Input
import com.fortify.cli.ftest._common.runner.FcliRunner
import com.fortify.cli.ftest._common.spec.Fcli
import com.fortify.cli.ftest._common.spec.FcliSessionType
import com.fortify.cli.ftest._common.spec.Prefix

Expand All @@ -16,19 +14,18 @@ import groovy.transform.CompileStatic
class FcliGlobalExtension implements IGlobalExtension {
@Override
public void start() {
FcliRunner.initialize();
Fcli.initialize();
}

@Override
public void stop() {
FcliSessionType.logoutAll()
FcliRunner.close()
Fcli.close()
}

@Override
void visitSpec(SpecInfo spec) {
updateNames(spec)
setFcliField(spec)
skipFeatures(spec)
}

Expand All @@ -42,22 +39,6 @@ class FcliGlobalExtension implements IGlobalExtension {
}
}

private void setFcliField(SpecInfo spec) {
// Register an interceptor for setting up any fields annotated with @Fcli
spec.allFields.each { FieldInfo field ->
if ( field.getAnnotation(Fcli.class) ) {
spec.addSetupInterceptor( { it ->
setFcliField(field, it.instance)
it.proceed()
})
}
}
}

private void setFcliField(FieldInfo fieldInfo, Object instance) {
instance.metaClass.setProperty(instance, fieldInfo.reflection.name, FcliRunner.&run)
}

private void skipFeatures(SpecInfo spec) {
// Exclude any features not matching any of the feature names
// listed in the fcli.run property
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package com.fortify.cli.ftest._common.spec;
/**
* Copyright 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.ftest._common.spec

import io.github.joke.spockoutputcapture.CapturedOutput
import io.github.joke.spockoutputcapture.OutputCapture
import spock.lang.Specification

/**
* Empty class for now, but we keep this in case we every need to
* have common fields again.
*
* @author Ruud Senden
*/
class FcliBaseSpec extends Specification {
@OutputCapture CapturedOutput out
@Fcli fcli
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.fortify.cli.ftest._common.spec;

import org.spockframework.runtime.extension.IMethodInterceptor

import com.fortify.cli.ftest._common.Fcli
import com.fortify.cli.ftest._common.Input
import com.fortify.cli.ftest._common.runner.FcliRunner

public enum FcliSessionType {
SSC(new SSCSessionHandler()),
Expand Down Expand Up @@ -39,7 +39,7 @@ public enum FcliSessionType {
if ( !loggedIn && !failed ) {
println("Logging in to "+friendlyName())
try {
loggedIn = FcliRunner.run(STD_LOGIN_ARGS+loginOptions())
loggedIn = Fcli.run(STD_LOGIN_ARGS+loginOptions())
failed = !loggedIn
} catch ( Exception e ) {
e.printStackTrace()
Expand All @@ -52,7 +52,7 @@ public enum FcliSessionType {
@Override
public synchronized final void logout() {
if ( loggedIn ) {
FcliRunner.run(STD_LOGOUT_ARGS+logoutOptions())
Fcli.run(STD_LOGOUT_ARGS+logoutOptions())
loggedIn = false
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package com.fortify.cli.ftest.core;

import com.fortify.cli.ftest._common.Fcli
import com.fortify.cli.ftest._common.spec.FcliBaseSpec
import com.fortify.cli.ftest._common.spec.Prefix

@Prefix("core.basic-info")
class BasicInfoSpec extends FcliBaseSpec {
def "help"(String[] args, boolean expectSuccess) {
expect:
fcli(args)==expectSuccess
out.lines
verifyAll(out.lines) {
it.any { it ==~ /.*Command-line interface for working with various Fortify products.*/ }
it.any { it.contains 'config' }
it.any { it.contains 'state' }
it.any { it.contains 'ssc' }
it.any { it.contains 'sc-dast' }
it.any { it.contains 'sc-sast' }
it.any { it.contains 'util' }
}
verifyAll(Fcli.run(args)) {
success==expectSuccess
verifyAll(expectSuccess ? stdout : stderr) {
it.any { it ==~ /.*Command-line interface for working with various Fortify products.*/ }
it.any { it.contains 'config' }
it.any { it.contains 'state' }
it.any { it.contains 'ssc' }
it.any { it.contains 'sc-dast' }
it.any { it.contains 'sc-sast' }
it.any { it.contains 'util' }
}
}

where:
args | expectSuccess
Expand All @@ -27,11 +29,12 @@ class BasicInfoSpec extends FcliBaseSpec {

def "version"() {
expect:
fcli "-V"
out.lines
verifyAll(out.lines) {
size()==1
it.any { it ==~ /.*fcli version \d+\.\d+\.\d+.*, built on.*/ }
verifyAll(Fcli.run("-V")) {
success
verifyAll(stdout) {
size()==1
it[0] ==~ /.*fcli version \d+\.\d+\.\d+.*, built on.*/
}
}
}
}
Loading

0 comments on commit 390eb9b

Please sign in to comment.