Skip to content

Commit

Permalink
[JENKINS-30582] add recording feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
lordofthejars committed Sep 22, 2015
1 parent 1040130 commit 6c327a7
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 91 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -22,7 +22,7 @@ There is a bit of a delay since we bring up Jenkins for every single test, with
it's own sandboxed workspace.

All executed tests are screen recorded by default, but only videos of failing tests are persited to `target` directory.
By default, vide file is named with the fully qualified test class name, minus sign (-) and the test method name.
By default, video file is named with the fully qualified test class name, minus sign (-) and the test method name.

If you want to persist all videos, the ones that succeeded too, you can set `RECORDER_SAVE_ALL` Java system property to true.

Expand Down
@@ -1,41 +1,20 @@
/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.test.acceptance.recorder;

import org.junit.runner.Description;
import org.monte.media.Format;
import org.monte.media.Registry;
import org.monte.screenrecorder.ScreenRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class JUnitScreenRecorder extends ScreenRecorder {

private static final Logger logger = LoggerFactory.getLogger(JUnitScreenRecorder.class);

private Description description;
private Format format;

Expand All @@ -49,9 +28,13 @@ public JUnitScreenRecorder(GraphicsConfiguration cfg, Rectangle captureArea, For

protected File createMovieFile(Format fileFormat) throws IOException {
if(!this.movieFolder.exists()) {
this.movieFolder.mkdirs();
} else if(!this.movieFolder.isDirectory()) {
throw new IOException("\"" + this.movieFolder + "\" is not a directory.");
if (!this.movieFolder.mkdirs()) {
logger.warn("Directory {} could not be created. mkdirs operation returned false.", this.movieFolder);
}
} else {
if(!this.movieFolder.isDirectory()) {
logger.warn("{} is not a directory.", this.movieFolder);
}
}

final File f = generateOutput(fileFormat);
Expand Down
@@ -1,26 +1,3 @@
/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.test.acceptance.recorder;

import org.junit.rules.TestRule;
Expand All @@ -32,6 +9,8 @@
import org.monte.media.VideoFormatKeys;
import org.monte.media.math.Rational;
import org.monte.screenrecorder.ScreenRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.io.File;
Expand All @@ -44,11 +23,13 @@
*/
public class TestRecorderRule extends TestWatcher {

private static final Logger logger = LoggerFactory.getLogger(TestRecorderRule.class);

private static final int MAX_RECORDING_TIME_SECS = 120000;
private static final int FRAME_RATE_PER_SEC = 60;
private static final int BIT_DEPTH = 16;
private static final float QUALITY_RATIO = 0.97f;
public static final String TARGET = "target";

private JUnitScreenRecorder screenRecorder;

Expand All @@ -65,7 +46,7 @@ private void startRecording(Description des) {
.getDefaultScreenDevice()
.getDefaultConfiguration();

File movieFolder = new File("target");
File movieFolder = new File(TARGET);

String mimeType = FormatKeys.MIME_QUICKTIME;
String videoFormatName = VideoFormatKeys.ENCODING_QUICKTIME_ANIMATION;
Expand All @@ -86,25 +67,27 @@ private void startRecording(Description des) {
outputFormatForScreenCapture, null, null, movieFolder, des);
this.screenRecorder.start();
} catch (IOException e) {
throw new IllegalArgumentException("Exception starting test recording.", e);
logger.warn("Exception starting test recording {}", e);
} catch (AWTException e) {
throw new IllegalArgumentException("Exception starting test recording.", e);
logger.warn("Exception starting test recording {}", e);
}
}

@Override
protected void succeeded(Description description) {
if (this.screenRecorder != null) {
stopRecording();
if(!saveAllExecutions()) {
if (saveAllExecutions()) {
stopRecordingWithFinalWaiting();
} else {
stopRecording();
this.screenRecorder.removeMovieFile();
}
}
}

@Override
protected void finished(Description description) {
stopRecording();
stopRecordingWithFinalWaiting();
}

private boolean isRecorderDisabled() {
Expand All @@ -116,18 +99,33 @@ private boolean saveAllExecutions() {
}

private void stopRecording() {
stopRecording(false);
}

private void stopRecordingWithFinalWaiting() {
stopRecording(true);
}

private void stopRecording(boolean waitTime) {
if (this.screenRecorder != null && this.screenRecorder.getState() == ScreenRecorder.State.RECORDING) {
try {
TimeUnit.SECONDS.sleep(1);
if (waitTime) {
waitUntilLastFramesAreRecorded();
}
screenRecorder.stop();
} catch (IOException e) {
throw new IllegalArgumentException("Exception stopping test recording.", e);
logger.warn("Exception stoping test recording {}.", e);
} catch (InterruptedException e) {
throw new IllegalArgumentException("Exception stopping test recording.", e);
logger.warn("Exception stoping test recording {}.", e);
}
}
}

private void waitUntilLastFramesAreRecorded() throws InterruptedException {
//Values below 500 milliseconds result in no recording latest frames.
TimeUnit.MILLISECONDS.sleep(500);
}

private Format getFileFormat(String mimeType) {
return new Format(FormatKeys.MediaTypeKey, FormatKeys.MediaType.FILE, FormatKeys.MimeTypeKey, mimeType);
}
Expand All @@ -142,7 +140,7 @@ private Format getOutputFormatForScreenCapture(String videoFormatName, String co
VideoFormatKeys.HeightKey, outputDimension.height,
VideoFormatKeys.DepthKey, bitDepth, FormatKeys.FrameRateKey, Rational.valueOf(screenRate),
VideoFormatKeys.QualityKey, quality,
FormatKeys.KeyFrameIntervalKey, screenRate * 10 // one keyframe per 10 seconds
FormatKeys.KeyFrameIntervalKey, screenRate * 5 // one keyframe per 5 seconds
);
}
}
@@ -1,26 +1,3 @@
/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.test.acceptance.recorder;

import org.hamcrest.core.Is;
Expand Down Expand Up @@ -59,15 +36,15 @@ public void shouldNotRecordSuccessTestExecutionByDefault() {
public void shouldRecordFailingTestExecutionByDefault() {

TestRecorderRule testRecorderRule = new TestRecorderRule();
Description shouldNotRecordSuccessTestExecutionByDefault = Description.createTestDescription("org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest", "shouldNotRecordSuccessTestExecutionByDefault");
Description shouldNotRecordSuccessTestExecutionByDefault = Description.createTestDescription("org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest", "shouldRecordFailingTestExecutionByDefault");
testRecorderRule.starting(shouldNotRecordSuccessTestExecutionByDefault);

System.out.println("Good Bye World");
//succeeded is not called since a failure is simulated
//testRecorderRule.succeeded(shouldNotRecordSuccessTestExecutionByDefault);
testRecorderRule.finished(shouldNotRecordSuccessTestExecutionByDefault);

File outputFile = new File("target", "org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest-shouldNotRecordSuccessTestExecutionByDefault.mov");
File outputFile = new File("target", "org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest-shouldRecordFailingTestExecutionByDefault.mov");
assertThat(outputFile.exists(), is(true));

//Clean the field
Expand All @@ -80,20 +57,22 @@ public void shouldRecordSuccessTestExecutionWhenSaveAll() {
System.setProperty("RECORDER_SAVE_ALL", "true");

TestRecorderRule testRecorderRule = new TestRecorderRule();
Description shouldNotRecordSuccessTestExecutionByDefault = Description.createTestDescription("org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest", "shouldNotRecordSuccessTestExecutionByDefault");
Description shouldNotRecordSuccessTestExecutionByDefault = Description.createTestDescription("org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest", "shouldRecordSuccessTestExecutionWhenSaveAll");
testRecorderRule.starting(shouldNotRecordSuccessTestExecutionByDefault);

System.out.println("Hello World");

testRecorderRule.succeeded(shouldNotRecordSuccessTestExecutionByDefault);
testRecorderRule.finished(shouldNotRecordSuccessTestExecutionByDefault);

File outputFile = new File("target", "org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest-shouldNotRecordSuccessTestExecutionByDefault.mov");
File outputFile = new File("target", "org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest-shouldRecordSuccessTestExecutionWhenSaveAll.mov");
assertThat(outputFile.exists(), is(true));

//Clean the field
if(oldValue != null) {
System.setProperty("RECORDER_SAVE_ALL", oldValue);
} else {
System.clearProperty("RECORDER_SAVE_ALL");
}
outputFile.delete();
}
Expand All @@ -104,20 +83,22 @@ public void shouldNotRecordWhenRecorderIsDisabled() {
System.setProperty("RECORDER_DISABLED", "true");

TestRecorderRule testRecorderRule = new TestRecorderRule();
Description shouldNotRecordSuccessTestExecutionByDefault = Description.createTestDescription("org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest", "shouldNotRecordSuccessTestExecutionByDefault");
Description shouldNotRecordSuccessTestExecutionByDefault = Description.createTestDescription("org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest", "shouldNotRecordWhenRecorderIsDisabled");
testRecorderRule.starting(shouldNotRecordSuccessTestExecutionByDefault);

System.out.println("Hello World");

//testRecorderRule.succeeded(shouldNotRecordSuccessTestExecutionByDefault);
testRecorderRule.finished(shouldNotRecordSuccessTestExecutionByDefault);

File outputFile = new File("target", "org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest-shouldNotRecordSuccessTestExecutionByDefault.mov");
File outputFile = new File("target", "org.jenkinsci.test.acceptance.recorder.TestRecorderRuleTest-shouldNotRecordWhenRecorderIsDisabled.mov");
assertThat(outputFile.exists(), is(false));

//Clean the field
if(oldValue != null) {
System.setProperty("RECORDER_DISABLED", oldValue);
} else {
System.clearProperty("RECORDER_DISABLED");
}
outputFile.delete();
}
Expand Down

0 comments on commit 6c327a7

Please sign in to comment.