//Copyright 2012-2023 Keysight Technologies // //Licensed under the Apache License, Version 2.0 (the "License"); //you may not use this file except in compliance with the License. //You may obtain a copy of the License at // //http://www.apache.org/licenses/LICENSE-2.0 // //Unless required by applicable law or agreed to in writing, software //distributed under the License is distributed on an "AS IS" BASIS, //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //See the License for the specific language governing permissions and //limitations under the License. using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; namespace OpenTap.Plugins.PluginDevelopment { // This example shows how to implement IArtifactListener // to make a result listener which can zip artifacts from the test plan run. [Display("Artifacts Zip")] public class ZipArtifactsResultListener : ResultListener, IArtifactListener { // Let the user select where to save the file. [FilePath] public MacroString ZipFile { get; set; } = new MacroString { Text = ".zip" }; public ZipArtifactsResultListener() { Name = "Zip"; } public override void OnTestPlanRunStart(TestPlanRun planRun) { // when the test plan starts, create the archive and close the stream. // after that the file will exist. // We will be opening and closing the file to insert items as needed. base.OnTestPlanRunStart(planRun); var fileName = GetTmpFileName(planRun); var fstr = File.OpenWrite(fileName); new ZipArchive(fstr, ZipArchiveMode.Create, false).Dispose(); } public override void OnTestPlanRunCompleted(TestPlanRun planRun, Stream logStream) { // When the test plan has completed, we can calculate the final name of it // and save it in the final location // and in addition publish it as an artifact. base.OnTestPlanRunCompleted(planRun, logStream); var fileName = GetTmpFileName(planRun); var finalName = ZipFile.Expand(planRun); try { // ensure that the folder exists before moving the file. var selectedFolder = Path.GetDirectoryName(Path.GetFullPath(finalName)); if (string.IsNullOrEmpty(selectedFolder) == false) Directory.CreateDirectory(selectedFolder); File.Move(fileName, finalName); } catch (IOException e) { if (e.Message.Contains("already exists")) { // try finding another name for the file. var ext = Path.GetExtension(finalName); if (ext == null) { for (int i = 2; i < 20; i++) { var name2 = $"{finalName}.{i}"; if (!File.Exists(name2)) { File.Move(fileName, name2); break; } } } else { for (int i = 2; i < 20; i++) { var name2 = Path.ChangeExtension(finalName, $"{i}{ext}"); if (!File.Exists(name2)) { File.Move(fileName, name2); finalName = name2; break; } } } } else { throw; } } this.Log.Debug("Wrote file: {0}", Path.GetFullPath(finalName)); // finally publish the artifact. // This must be done asynchronously as we are currently inside a result processing thread. planRun.PublishArtifactAsync(File.OpenRead(finalName), Path.GetFileName(finalName)); } // this map is used to keep track of the parent-child association between step runs and plan runs. readonly Dictionary parentMap = new Dictionary(); public override void OnTestStepRunStart(TestStepRun stepRun) { base.OnTestStepRunStart(stepRun); parentMap.Add(stepRun.Id, stepRun.Parent); } public override void OnTestStepRunCompleted(TestStepRun stepRun) { base.OnTestStepRunCompleted(stepRun); parentMap.Remove(stepRun.Id); } Guid GetPlanRunId(Guid runId) { // get the plan run id of any step run, based on the id. // this assumes that the run id comes from the plan // and that the test plan run id is the topmost (has no parent). while (parentMap.TryGetValue(runId, out var runId2)) { runId = runId2; } return runId; } string GetTmpFileName(TestRun run) { var id = GetPlanRunId(run.Id); var name = $"tmp.artifacts.{id}.zip"; return name; } // this is the interface required by IArtifactListener // assume the artifactStream is a stream of data and artifactName is the name of the artifact // including possible extensions like 'png' or 'csv'. public void OnArtifactPublished(TestRun run, Stream artifactStream, string artifactName) { try { var fileName = GetTmpFileName(run); if (!File.Exists(fileName)) return; // The means file has been finalized (or something went wrong). After this we don't insert new data. // insert the object data into the zip file. var rawStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete); using (var archive = new ZipArchive(rawStream, ZipArchiveMode.Update, false)) { var entry = archive.CreateEntry(artifactName); using (var s2 = entry.Open()) { artifactStream.CopyTo(s2); } } } finally { artifactStream.Close(); } } } }