message: [RM] Bug Incorrect Cancel BB.

diff:
******************************************************************************/
package com.exactpro.sf.bigbutton.execution;
+import static com.exactpro.sf.scriptrunner.state.ScriptStatus.CANCELED;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.exactpro.sf.SFAPIClient;
import com.exactpro.sf.Service.Status;
import com.exactpro.sf.ServiceImportResult;
@@ -53,40 +88,11 @@ import com.exactpro.sf.testwebgui.restapi.xml.XmlTestscriptActionResponse;
import com.exactpro.sf.util.EMailUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Iterators;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.URL;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.Spliterator;
-import java.util.Spliterators;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
public class ExecutorClient {
+ private static final int TIMEOUT_FOR_CHECK_TEST_SCRIPT_STATUS = 500;
+
private static final Logger logger = LoggerFactory.getLogger(ExecutorClient.class);
private static final String BB_ENVIRONMENT = "bb__automation";
@@ -710,7 +716,7 @@ public class ExecutorClient {
ScriptState scriptState;
while (!(scriptState = apiClient.getTestScriptRunInfo(testScriptId).getScriptState()).isTerminateState()
&& scriptState != ScriptState.READY) {
- Thread.sleep(1000);
+ Thread.sleep(TIMEOUT_FOR_CHECK_TEST_SCRIPT_STATUS);
}
} catch (APICallException | APIResponseException e) {
throw new EPSCommonException("Could not get scripts state for test script with id + {" + testScriptId + '}', e);
@@ -773,7 +779,6 @@ public class ExecutorClient {
monitor.scriptExecuted(script, (int)scriptRunId, executor, relativeListReportsFolder,
reportDownloadNeeded, isMasterSf);
- apiClient.deleteMatrix((int)matrixId);
} catch (InterruptedException e) {
throw e;
} catch (Exception e) {
@@ -990,6 +995,7 @@ public class ExecutorClient {
idleTime = 0;
+ deleteMatrix();
cleanScriptListEnvironment(); // Cleanup
} catch (InterruptedException e) { //TODO ?
@@ -1166,22 +1172,36 @@ public class ExecutorClient {
toErrorState(e);
}
}
+
private void stopRunningTestScripts() {
try {
// currentList may be null when BB end to running all test scripts.
// When BB execute action Interrupt currentList is not null
if (currentList != null) {
- currentList
+
+ List<Script> notExecutedScripts = currentList
.getScripts()
.stream()
.filter(script -> !script.isFinished())
- .forEach(script -> {
+ .collect(Collectors.toList());
+
+ notExecutedScripts.forEach(script -> {
try {
apiClient.stopTestScriptRun((int)script.getRemoteInformation().getTestScriptId());
- apiClient.deleteTestScriptRun((int) script.getRemoteInformation().getTestScriptId());
- apiClient.deleteMatrix((int)script.getRemoteInformation().getMatrixId());
} catch (APICallException | APIResponseException e) {
- throw new EPSCommonException("Could not perform api call", e);
+ throw new EPSCommonException("Could not perform api call for stop test script with id: " + script.getRemoteInformation().getTestScriptId(), e);
+ }
+ });
+
+ notExecutedScripts.forEach(script -> {
+ int testScriptId = (int)script.getRemoteInformation().getTestScriptId();
+ try {
+ waitForStopTestScript(testScriptId);
+ if (apiClient.getTestScriptRunInfo(testScriptId).getScriptStatus() == CANCELED) {
+ apiClient.deleteTestScriptRun(testScriptId);
+ }
+ } catch (InterruptedException | APICallException | APIResponseException e) {
+ throw new EPSCommonException("Could not perform api call for delete test script with id: " + script.getRemoteInformation().getTestScriptId(), e);
}
});
}
@@ -1190,5 +1210,33 @@ public class ExecutorClient {
}
}
+ private void waitForStopTestScript(int testScriptId) throws InterruptedException {
+ try {
+ //FIXME: !!! isTerminateState in ScriptState is mean that TestScriptRunReport may not locked.
+ // If report isn`t locked we can`t delete testScript
+ while (apiClient.getTestScriptRunShortReport(testScriptId).isLocked()) {
+ Thread.sleep(TIMEOUT_FOR_CHECK_TEST_SCRIPT_STATUS);
+ }
+ } catch (APIResponseException | APICallException e) {
+ throw new EPSCommonException("Could not perform api call for getting test script status with id: " + testScriptId, e);
+ }
+ }
+
+ private void deleteMatrix() {
+ try {
+ if (currentList != null) {
+ currentList.getScripts().forEach(script -> {
+ int matrixId = (int)script.getRemoteInformation().getMatrixId();
+ try {
+ apiClient.deleteMatrix(matrixId);
+ } catch (APICallException | APIResponseException e) {
+ throw new EPSCommonException("Could not perform api call for delete matrix with id: " + matrixId, e);
+ }
+ });
+ }
+ } catch (RuntimeException e) {
+ toErrorState(e);
+ }
+ }
}

original_message:
[RM] Bug #63571. Incorrect Cancel BB.

Change-Id: I46a85deeaba9c3eae3c740ac1003ca817dc50d23


message: Feature Don't render the expand button for the actions that have no sub elements

change_type: MODIFY

diff:
@@ -26,24 +26,35 @@ interface PanelProps {
isExpanded?: boolean;
onExpand?: (isExpanded: boolean) => void;
children: [React.ReactNode | ((onExpand: () => void) => React.ReactNode), React.ReactNode];
+ isExpandDisabled?: boolean;
}
-export const ExpandablePanel = ({ children, isExpanded, onExpand }: PanelProps) => {
+export const ExpandablePanel = ({ children, isExpanded, onExpand, isExpandDisabled }: PanelProps) => {
+ const rootClass = createStyleSelector(
+ "expandable-panel",
+ isExpandDisabled? "disabled": null
+ )
const iconClass = createStyleSelector(
"expandable-panel__icon",
!isExpanded ? "hidden" : null
);
+ const expand = (isExpanded: boolean) => {
+ if (!isExpandDisabled) {
+ onExpand(isExpanded)
+ }
+ }
+
const [header, body] = children;
return (
-

+

+ onClick={stopPropagationHandler(expand, !isExpanded)}/>
{
typeof header == 'function' ?
- header(() => onExpand(!isExpanded)) :
+ header(() => expand(!isExpanded)) :
header
}

diff:
@@ -58,6 +58,15 @@ export function ActionCard({ action, children, isSelected, onSelect, isRoot, isT
outcome
} = action;
+ const isEmptyParameters = parameters === null,
+ isSingleEmptyMessage = !isEmptyParameters && parameters.length == 1 && parameters[0].name === "Message" &&
+ parameters[0].value === "" && parameters[0].subParameters.length == 0,
+ showParams = !isEmptyParameters && !isSingleEmptyMessage,
+ isDisbled = !showParams &&
+ !action.isTruncated &&
+ !children.length &&
+ !(action.status.status == StatusType.FAILED && action.status.cause != null)
+
const rootClassName = createStyleSelector(
"action-card",
status.status,
@@ -79,16 +88,12 @@ export function ActionCard({ action, children, isSelected, onSelect, isRoot, isT
const elapsedTime = getSecondsPeriod(startTime, finishTime);
- const isEmptyParameters = parameters === null,
- isSingleEmptyMessage = !isEmptyParameters && parameters.length == 1 && parameters[0].name === "Message" &&
- parameters[0].value === "" && parameters[0].subParameters.length == 0,
- showParams = !isEmptyParameters && !isSingleEmptyMessage
-
return (

<ExpandablePanel
+ isExpandDisabled={isDisbled}
isExpanded={isExpanded}
onExpand={isExpanded => onRootExpand(isExpanded)}>
{
@@ -167,7 +172,8 @@ export function ActionCard({ action, children, isSelected, onSelect, isRoot, isT
}

- {showParams &&

+ {showParams &&
+

<SearchExpandablePanel
searchKeyPrefix={keyForAction(id, 'parameters')}
stateKey={keyForAction(id, 'parameters')}>
@@ -182,21 +188,19 @@ export function ActionCard({ action, children, isSelected, onSelect, isRoot, isT
params={parameters}
name={name} />
</SearchExpandablePanel>
-
}
-
- {action.isTruncated ? (
+

+ }
+ {action.isTruncated &&

{action.verificationCount - action.subNodes.filter(node => isVerification(node)).length} verifications were truncated

- ) : null}
-
+ }
{
// rendering inner nodes
children
}
- {
- action.status.status == StatusType.FAILED && action.status.cause != null ? (
+ {action.status.status == StatusType.FAILED && action.status.cause != null &&

<RecoverableExpandablePanel
stateKey={keyForAction(id, 'status')}>
@@ -210,7 +214,6 @@ export function ActionCard({ action, children, isSelected, onSelect, isRoot, isT
stateKey={`${keyForAction(id, 'status')}-exception`} />
</RecoverableExpandablePanel>

- ) : null
}

</ExpandablePanel>

diff:
}
}
}
+
+.disabled .expandable-panel {
+ &__icon {
+ opacity: 0.2;
+ }
+}

license: Apache License 2.0

repo: exactpro/sailfish-core

original_message:
Feature #63595. Don't render the expand button for the actions that have no sub elements

Change-Id: I1a35a08e3274823d5eb1b9afc02dea44328ab2af


message: Feature Add a badge for the logs tab

diff:
@@ -595,8 +595,15 @@ public class JsonReport implements IScriptReport {
public void createLogTable(List<String> header, List<LoggerRow> rows) {
assertState(ContextType.TESTCASE, ContextType.ACTION, ContextType.ACTIONGROUP);
-
- rows.stream().map(LogEntry::new).forEachOrdered(jsonpTestcaseWriter::write);
+ TestCase testCase = context.getFirstParentNode(TestCase.class);
+ List<LogEntry> entries = rows.stream().map(LogEntry::new).collect(Collectors.toList());
+ Set<String> levels = entries.stream().map(LogEntry::getLevel).collect(Collectors.toSet());
+ testCase.setHasErrorLogs(levels.contains("ERROR"));
+ testCase.setHasWarnLogs(levels.contains("WARN"));
+
+ exportToFile(testCase, testCase.getName(), false);
+ jsonpTestcaseWriter.updateTestCaseFile(testCase);
+ entries.forEach(jsonpTestcaseWriter::write);
}
public void setOutcomes(OutcomeCollector outcomes) {

diff:
@@ -47,6 +47,8 @@ public class TestCase implements IJsonReportNode {
private int hash;
private String description;
private Status status;
+ private boolean hasErrorLogs;
+ private boolean hasWarnLogs;
@JsonIgnore private final BugCategory bugRoot;
@JsonIgnore private Map<Bug, List<String>> bugToCategoryMap;
@@ -202,4 +204,20 @@ public class TestCase implements IJsonReportNode {
public void setTags(Set<String> tags) {
this.tags = tags;
}
+
+ public boolean hasErrorLogs() {
+ return hasErrorLogs;
+ }
+
+ public void setHasErrorLogs(boolean hasErrorLogs) {
+ this.hasErrorLogs = hasErrorLogs;
+ }
+
+ public boolean hasWarnLogs() {
+ return hasWarnLogs;
+ }
+
+ public void setHasWarnLogs(boolean hasWarnLogs) {
+ this.hasWarnLogs = hasWarnLogs;
+ }
}

diff:
@@ -19,7 +19,6 @@ package com.exactpro.sf.scriptrunner.impl.jsonreport.beans
import com.exactpro.sf.scriptrunner.impl.jsonreport.IJsonReportNode
import com.exactpro.sf.scriptrunner.impl.jsonreport.JsonpChunkedDataWriter
import com.exactpro.sf.scriptrunner.impl.jsonreport.helpers.WorkspaceNode
-import java.nio.file.Path
import java.time.Instant
class JsonpTestcase(
@@ -45,6 +44,8 @@ class JsonpTestcase(
val description: String? = testCase.description
val status: Status? = testCase.status
val bugs: Collection<IJsonReportNode> = testCase.bugTree
+ val hasErrorLogs: Boolean = testCase.hasErrorLogs()
+ val hasWarnLogs: Boolean = testCase.hasWarnLogs()
val indexFiles: Map<String, String> = dataWriters.asSequence().associate {
it.key.simpleName.toLowerCase() to reportRoot.toAbsolutePath().relativize(it.value.indexFile.toAbsolutePath()).toString()

diff:
@@ -88,7 +88,9 @@ export function createTestCase(id: string = '0', actions: ActionNode[] = [], mes
hash: 0,
status: { status },
startTime: new Date().toString(),
- finishTime: new Date().toString()
+ finishTime: new Date().toString(),
+ hasErrorLogs: false,
+ hasWarnLogs: false
}
}

diff:
@@ -62,14 +62,16 @@ class LeftPanelBase extends React.Component<Props> {
<div className="layout-panel__tabs">
<ToggleButton
isToggled={panel == Panel.ACTIONS}
- onClick={() => this.selectPanel(Panel.ACTIONS)}
- text="Actions" />
+ onClick={() => this.selectPanel(Panel.ACTIONS)}>
+ Actions
+ </ToggleButton>
<ToggleButton
isToggled={panel == Panel.STATUS}
isDisabled={!statusEnabled}
onClick={statusEnabled ? (() => this.selectPanel(Panel.STATUS)) : undefined}
- text="Status"
- title={statusEnabled ? null : "No status info"}/>
+ title={statusEnabled ? null : "No status info"}>
+ Status
+ </ToggleButton>
</div>
{
isCheckpointsEnabled ? (

diff:
@@ -150,13 +150,15 @@ export class ReportLayoutBase extends React.Component<Props, State> {
<ToggleButton
textClass="report-summary__button_text"
isToggled={this.state.panel == Panel.ReportSummary}
- onClick={changePanel(Panel.ReportSummary)}
- text="Report Summary"/>
+ onClick={changePanel(Panel.ReportSummary)}>
+ Report Summary
+ </ToggleButton>
<ToggleButton
textClass="report-summary__button_text"
isToggled={this.state.panel == Panel.RunInfo}
- onClick={changePanel(Panel.RunInfo)}
- text="Run Information"/>
+ onClick={changePanel(Panel.RunInfo)}>
+ Run Information
+ </ToggleButton>
</div>
{
this.getPanel()

diff:
@@ -31,7 +31,7 @@ import {
togglePredictions,
uglifyAllMessages
} from '../actions/actionCreators';
-import { createStyleSelector, createTriStateControlClassName } from '../helpers/styleCreators';
+import { createStyleSelector, createTriStateControlClassName, createBemElement } from '../helpers/styleCreators';
import { isAction } from "../models/Action";
import { KnownBugPanel } from "./knownbugs/KnownBugPanel";
import { StatusType } from '../models/Status';
@@ -51,6 +51,8 @@ interface StateProps {
hasKnownBugs: boolean;
beautifiedMessagesEnabled: boolean;
hasLogs: boolean;
+ hasErrorLogs: boolean;
+ hasWarningLogs: boolean;
}
interface DispatchProps {
@@ -114,10 +116,13 @@ class RightPanelBase extends React.Component<Props, State> {
const {
panel, rejectedMessagesEnabled, adminMessages, adminMessagesEnabled, adminEnabledHandler, selectCurrentRejectedMessage,
predictionsEnabled, predictionsAvailable, beautifiedMessagesEnabled, uglifyAllHandler, hasLogs, hasKnownBugs,
+ hasErrorLogs, hasWarningLogs
} = this.props;
const adminControlEnabled = adminMessages.length != 0;
+ const logChipClassName = createBemElement('log-button', 'chip', hasErrorLogs? 'error': hasWarningLogs? 'warning': 'hiden')
+
const adminRootClass = createTriStateControlClassName("layout-control", adminMessagesEnabled, adminControlEnabled),
adminIconClass = createTriStateControlClassName("layout-control__icon admin", adminMessagesEnabled, adminControlEnabled),
adminTitleClass = createTriStateControlClassName("layout-control__title selectable", adminMessagesEnabled, adminControlEnabled),
@@ -137,18 +142,24 @@ class RightPanelBase extends React.Component<Props, State> {

<ToggleButton
isToggled={panel == Panel.MESSAGES}
- onClick={() => this.selectPanel(Panel.MESSAGES)}
- text="Messages"/>
+ onClick={() => this.selectPanel(Panel.MESSAGES)}>
+ Messages
+ </ToggleButton>
<ToggleButton
isToggled={panel == Panel.KNOWN_BUGS}
isDisabled={!hasKnownBugs}
- onClick={() => this.selectPanel(Panel.KNOWN_BUGS)}
- text="Known bugs"/>
+ onClick={() => this.selectPanel(Panel.KNOWN_BUGS)}>
+ Known bugs
+ </ToggleButton>
<ToggleButton
isDisabled={!hasLogs}
isToggled={panel == Panel.LOGS}
- onClick={() => this.selectPanel(Panel.LOGS)}
- text="Logs" />
+ onClick={() => this.selectPanel(Panel.LOGS)}>
+

+
Logs

+

+

+ </ToggleButton>

{
beautifiedMessagesEnabled ? (
@@ -272,7 +283,9 @@ export const RightPanel = connect(
predictionsEnabled: state.machineLearning.predictionsEnabled,
hasKnownBugs: state.selected.testCase.bugs.length > 0,
- hasLogs: state.selected.testCase.files.logentry.count > 0
+ hasLogs: state.selected.testCase.files.logentry.count > 0,
+ hasErrorLogs: state.selected.testCase.hasErrorLogs,
+ hasWarningLogs: state.selected.testCase.hasWarnLogs
}),
(dispatch) => ({
panelSelectHandler: (panel: Panel.MESSAGES | Panel.KNOWN_BUGS | Panel.LOGS) => dispatch(setRightPane(panel)),

diff:
@@ -18,17 +18,26 @@ import * as React from 'react';
import '../styles/buttons.scss';
import { createStyleSelector } from '../helpers/styleCreators';
+type ToggleButtonContent = string | React.ReactNode
+
interface Props {
onClick?: Function;
isToggled?: boolean;
isDisabled?: boolean;
- text: string;
title?: string;
theme?: string;
textClass?: string;
+ children: ToggleButtonContent;
}
-export function ToggleButton({ onClick = () => {}, isToggled = false, text, textClass, theme = 'default', isDisabled = false, title = '' }: Props) {
+export function ToggleButton({ onClick = () => { },
+ isToggled = false,
+ theme = 'default',
+ isDisabled = false,
+ title = '',
+ textClass,
+ children: content }: Props) {
+
const className = createStyleSelector(
"toggle-button",
theme,
@@ -36,10 +45,18 @@ export function ToggleButton({ onClick = () => {}, isToggled = false, text, text
isToggled ? "toggled" : null
);
+ if (textClass) {
+ content = (
+
+ {content}
+
+ )
+ }
+
return (
-
 !isDisabled && onClick(text)} title={title}>
+
 !isDisabled && onClick(content)} title={title}>

-
{text}

+ {content}

)

diff:
@@ -43,6 +43,8 @@ export default interface TestCase {
indexFiles?: TestCaseIndexFiles;
files?: TestCaseFiles;
lastUpdate?: string;
+ hasErrorLogs: boolean;
+ hasWarnLogs: boolean;
}
interface TestCaseIndexFiles {

diff:
}
}
+.log-button {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+
+ &__chip {
+ margin-left: 12px;
+ border-radius: 5px;
+ width: 12px;
+ height: 12px;
+
+ &.error {
+ background: #E23636;
+ }
+
+ &.warning {
+ background: #F57A3D;
+ }
+
+ &.hiden {
+ display: none;
+ }
+ }
+}
+
.tc-card {
@include status-card-with-running;

original_message:
Feature #63303. Add a badge for the logs tab

Change-Id: Ic2efae5330e0428e9f99ee61a25cc8448431c586


message: Feature Implement ml data collection progress bar and test case ml indicators

diff:
@@ -25,15 +25,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

diff:
@@ -24,7 +24,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
+import com.exactpro.sf.scriptrunner.StatusType;
import com.exactpro.sf.scriptrunner.impl.jsonreport.IJsonReportNode;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -90,6 +92,38 @@ public class TestCase implements IJsonReportNode {
return this.bugToCategoryMap;
}
+ @JsonIgnore
+ public List<Action> getRootActions() {
+ return this.actions.stream().filter(n -> n instanceof Action).map(n -> (Action)n).collect(Collectors.toList());
+ }
+
+ @JsonIgnore
+ public Long getFirstActionId() {
+ List<Action> rootActions = getRootActions();
+ if (rootActions.size() == 0) {
+ return null;
+ }
+ else {
+ return rootActions.get(0).getId();
+ }
+ }
+
+ @JsonIgnore
+ public Long getLastActionId() {
+ List<Action> rootActions = getRootActions();
+ if (rootActions.size() == 0) {
+ return null;
+ }
+ else {
+ return rootActions.get(rootActions.size() - 1).getId();
+ }
+ }
+
+ @JsonIgnore
+ public int getFailedActionsCount() {
+ return (int)getRootActions().stream().filter(a -> a.getStatus().getStatus() == StatusType.FAILED).count();
+ }
+
public List<IJsonReportNode> getActions() {
return actions;
}

diff:
@@ -32,6 +32,9 @@ public class TestCaseMetadata {
private String id;
private int hash;
private String description;
+ private Long firstActionId;
+ private Long lastActionId;
+ private int failedActionCount;
private Collection<IJsonReportNode> bugs;
@@ -55,6 +58,9 @@ public class TestCaseMetadata {
this.jsonFileName = jsonFileName;
this.jsonpFileName = jsonpFileName;
this.tags = testCase.getTags();
+ this.firstActionId = testCase.getFirstActionId();
+ this.lastActionId = testCase.getLastActionId();
+ this.failedActionCount = testCase.getFailedActionsCount();
}
@@ -153,4 +159,28 @@ public class TestCaseMetadata {
public void setJsonpFileName(String jsonpFileName) {
this.jsonpFileName = jsonpFileName;
}
+
+ public Long getFirstActionId() {
+ return firstActionId;
+ }
+
+ public void setFirstActionId(Long firstActionId) {
+ this.firstActionId = firstActionId;
+ }
+
+ public Long getLastActionId() {
+ return lastActionId;
+ }
+
+ public void setLastActionId(Long lastActionId) {
+ this.lastActionId = lastActionId;
+ }
+
+ public int getFailedActionCount() {
+ return failedActionCount;
+ }
+
+ public void setFailedActionCount(int failedActionCount) {
+ this.failedActionCount = failedActionCount;
+ }
}

diff:
@@ -32,12 +32,16 @@ import { ToggleButton } from './ToggleButton';
import RunInformation from './RunInformation';
import ReportSummary from './ReportSummary';
import Tag from './Tag';
+import { SubmittedData } from '../models/MlServiceResponse';
+import TestCase from '../models/TestCase';
const OLD_REPORT_PATH = 'report.html';
interface StateProps {
report: ReportState;
selectedTestCaseId: string;
+ submittedData: SubmittedData[];
+ mlEnabled: boolean;
}
interface Props extends StateProps { }
@@ -51,6 +55,7 @@ interface State {
export class ReportLayoutBase extends React.Component<Props, State> {
+
constructor(props) {
super(props);
@@ -79,6 +84,24 @@ export class ReportLayoutBase extends React.Component<Props, State> {
})
}
+ isMLSubmitted(metadata: TestCaseMetadata) {
+ const { submittedData } = this.props;
+ const submittedCount = [...new Set(submittedData.map(m => m.actionId))]
+ .filter(actionId => actionId >= metadata.firstActionId &&
+ actionId <= metadata.lastActionId).length;
+
+ return submittedCount === metadata.failedActionCount && submittedCount != 0;
+ }
+
+ calculateMLStats() {
+ const { submittedData, report } = this.props;
+
+ let uniqueActionId = new Set(submittedData.map(m => m.actionId)).size,
+ failedActions = report.metadata.map(m => m.failedActionCount).reduce((a,b) => a + b);
+
+ return uniqueActionId / failedActions
+ }
+
render() {
const changePanel= (panel: Panel) => {
return () => {
@@ -87,14 +110,18 @@ export class ReportLayoutBase extends React.Component<Props, State> {
}
const { report, selectedTestCaseId } = this.props,
- { showKnownBugs } = this.state;
+ { showKnownBugs } = this.state,
+ mlStats = (this.calculateMLStats() * 100).toPrecision(2) + "%";
const filteredMetadata = report.metadata.filter(isTestCaseMetadata),
knownBugsPresent = filteredMetadata.some(item => item.bugs != null && item.bugs.length > 0),
knownBugsClass = showKnownBugs ? "active" : "enabled",
failedTestCasesEnabled = filteredMetadata.some(({status}) => status.status === StatusType.FAILED),
failedTcTitleClass = createBemElement('report', 'title', failedTestCasesEnabled ? 'failed': 'disabled'),
- isLive = report.finishTime == null;
+ isLive = report.finishTime == null,
+ alerts = report.alerts || [],
+ tags = report.tags || [],
+ mlStatsClass = createStyleSelector('ml-stats', this.props.mlEnabled? null: "hidden");
const knownBugsButton = (
knownBugsPresent ?
@@ -135,6 +162,13 @@ export class ReportLayoutBase extends React.Component<Props, State> {

<FailedTestCaseCarousel/>
{knownBugsButton}
+

+ ML Collected
+

+

+

+ {mlStats}
+

@@ -179,7 +213,8 @@ export class ReportLayoutBase extends React.Component<Props, State> {
isSelected={metadata.id === selectedTestCaseId}
key={index}
metadata={metadata}
- index={index + 1}/>
+ index={index + 1}
+ isMLSubmitted={this.isMLSubmitted(metadata)}/>
))
}
</HeatmapScrollbar>
@@ -197,7 +232,9 @@ export class ReportLayoutBase extends React.Component<Props, State> {
const ReportLayout = connect(
(state: AppState): StateProps => ({
report: state.report,
- selectedTestCaseId: state.selected.selectedTestCaseId
+ selectedTestCaseId: state.selected.selectedTestCaseId,
+ submittedData: state.machineLearning.submittedData,
+ mlEnabled: state.machineLearning.token != null
})
)(ReportLayoutBase);

diff:
@@ -18,7 +18,7 @@ import * as React from 'react';
import {TestCaseMetadata, isTestCaseMetadata} from '../models/TestcaseMetadata';
import {formatTime, getSecondsPeriod} from '../helpers/date';
import '../styles/report.scss';
-import {createStyleSelector} from '../helpers/styleCreators';
+import { createStyleSelector, createBemElement } from '../helpers/styleCreators';
import {KnownBugIndicator} from "./knownbugs/KnownBugIndicator";
import {KnownBugSummary} from "./knownbugs/KnownBugSummary";
import {connect} from "react-redux";
@@ -33,6 +33,7 @@ interface OwnProps {
knownBugsEnabled: boolean;
index: number;
isSelected: boolean;
+ isMLSubmitted: boolean;
}
interface DispatchProps {
@@ -41,7 +42,7 @@ interface DispatchProps {
interface Props extends OwnProps, DispatchProps {}
-function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSelected }: Props) {
+function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSelected, isMLSubmitted }: Props) {
const baseRef = React.useRef<HTMLDivElement>();
@@ -67,7 +68,9 @@ function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSe
"tc-card",
status,
isSelected ? "selected" : null
- );
+ ), mlSubmittedBoxClass = createBemElement("tc-card",
+ "ml-submitted",
+ isMLSubmitted? null: "hidden");
const baseClickHandler = () => {
// Don't trigger 'click' event when user selects text
@@ -98,6 +101,9 @@ function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSe
null
}

+

+

+

{status}

diff:
@@ -31,6 +31,9 @@ export interface TestCaseMetadata {
jsonFileName: string;
jsonpFileName: string;
bugs: (KnownBug | KnownBugCategory) [];
+ firstActionId: number;
+ lastActionId: number;
+ failedActionCount: number;
}
export function isTestCaseMetadata(testCase: TestCaseMetadata | LiveTestCase): testCase is TestCaseMetadata {

diff:
}
}
+.ml-stats {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-left: 20px;
+
+ &__title {
+ @include unselectable;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 19px;
+ color: #45A155;
+ white-space: nowrap;
+ margin-right: 8px;
+ }
+
+ &__bar {
+ width: 200px;
+ height: 10px;
+ background-color: #CCCCCC;
+ }
+
+ &__progress-bar {
+ background-color: #59B368;
+ height: 100%;
+ }
+
+ &__bar, &__progress-bar {
+ border-radius: 5px;
+ }
+
+ &__percents {
+ @include unselectable;
+ margin-left: 8px;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 19px;
+ text-align: right;
+
+ color: #3D994D;
+ }
+
+ &.hidden {
+ display: none;
+ }
+}
+
.tc-card {
@include status-card-with-running;
width: 100%;
display: grid;
grid-template: auto auto / 55px 1fr auto;
- grid-template-areas: "index title status"
- "index info elapsed-time"
- "divider divider divider"
- "bugs-indicator bugs-summary bugs-summary";
+ grid-template-areas: "index title status ml"
+ "index info elapsed-time ml"
+ "divider divider divider ml"
+ "bugs-indicator bugs-summary bugs-summary ml";
border: 2px solid var(--status-secondary-border-color);
border-radius: 5px;
padding-left: 15px;
}
+ &__ml-submitted {
+ margin-left: 16px;
+ grid-area: ml;
+ align-self: center;
+
+ &.icon {
+ @include icon(url(../../resources/icons/ml-message-submitted.svg), 22px, 22px);
+ }
+
+ &.hidden {
+ display: none;
+ }
+ }
+
&__status {
grid-area: status;
}

original_message:
Feature #63126. Implement ml data collection progress bar and test case ml indicators

Change-Id: I6842807ac8984a67800270d281002c214ef123b5


message: Bug Logs panel doesn't scrolled to the top on the panel's button click.

diff:
}
},
"@babel/runtime": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz",
- "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==",
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
"requires": {
- "regenerator-runtime": "^0.13.2"
+ "regenerator-runtime": "^0.13.4"
}
},
"@babel/template": {
}
},
"@types/react-redux": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.2.tgz",
- "integrity": "sha512-Iim6UCtD0mZX9U3jBuT6ZObBZ8UlakoOgefiRgi5wakfbNnXd3TUwwUMgi3Ijc0fxsPLZ5ULoz0oDy15YIaLmQ==",
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.7.tgz",
+ "integrity": "sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
}
},
"hoist-non-react-statics": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
- "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
"requires": {
"loose-envify": "^1.0.0"
}
"integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw=="
},
"react-redux": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz",
- "integrity": "sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
+ "integrity": "sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==",
"requires": {
"@babel/runtime": "^7.5.5",
"hoist-non-react-statics": "^3.3.0",
- "invariant": "^2.2.4",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.9.0"
"dev": true
},
"regenerator-runtime": {
- "version": "0.13.3",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
- "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g=="
},
"regex-not": {
"version": "1.0.2",

diff:
},
"author": "vladislav.kotelnikov",
"dependencies": {
+ "core-js": "3.6.4",
"react": "^16.11.0",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^16.11.0",
"react-input-autosize": "^2.2.2",
- "react-redux": "^7.1.3",
+ "react-redux": "^7.2.0",
"react-virtuoso": "^0.12.3",
"redux": "^4.0.4",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
- "setimmediate": "^1.0.5",
- "core-js": "3.6.4",
- "resize-observer-polyfill": "^1.5.1"
+ "resize-observer-polyfill": "^1.5.1",
+ "setimmediate": "^1.0.5"
},
"devDependencies": {
"@types/enzyme": "^3.10.3",
"@types/react-custom-scrollbars": "^4.0.6",
"@types/react-dom": "^16.9.4",
"@types/react-input-autosize": "^2.0.1",
- "@types/react-redux": "^7.1.2",
+ "@types/react-redux": "^7.1.7",
"@types/redux-mock-store": "^1.0.1",
"awesome-typescript-loader": "^5.2.1",
"cross-env": "^6.0.3",

diff:
@@ -21,7 +21,7 @@ import Panel from '../util/Panel';
import Message from '../models/Message';
import { ToggleButton } from './ToggleButton';
import { MessagesCardList, MessagesCardListBase } from './message/MessagesCardList';
-import LogsList from './log/LogsList';
+import LogsList, { LogListActions } from './log/LogsList';
import AppState from '../state/models/AppState';
import { isAdmin, isRejected } from '../helpers/messageType';
import {
@@ -72,6 +72,7 @@ interface State {
class RightPanelBase extends React.Component<Props, State> {
private messagesPanel = React.createRef<MessagesCardListBase>();
+ private logsPanel = React.createRef<LogListActions>();
private root = React.createRef<HTMLDivElement>();
private rootResizeObserver = new ResizeObserver(elements => {
@@ -97,6 +98,10 @@ class RightPanelBase extends React.Component<Props, State> {
this.messagesPanel.current?.scrollToTop();
break;
}
+ case (Panel.LOGS): {
+ this.logsPanel.current?.scrollToTop();
+ break;
+ }
default: {
return;
@@ -242,6 +247,7 @@ class RightPanelBase extends React.Component<Props, State> {

<LogsList
+ ref={this.logsPanel}
isActive={this.props.panel === Panel.LOGS} />

diff:
@@ -45,6 +45,10 @@ interface State {
scrolledIndex: Number;
}
+interface OwnProps {
+ ref?: React.MutableRefObject<ActionsListBase>
+}
+
export class ActionsListBase extends React.PureComponent<Props, State> {
private list = React.createRef<VirtualizedList>();
@@ -125,7 +129,7 @@ export class ActionsListBase extends React.PureComponent<Props, State> {
};
}
-export const ActionsList = connect(
+export const ActionsList = connect<Props, {}, OwnProps, Props & OwnProps>(
(state: AppState): Props => ({
actions: getIsFilterApplied(state) && !state.filter.isTransparent ?
getFilteredActions(state) :

diff:
@@ -23,7 +23,7 @@ import { loadLogs } from '../../thunks/loadTestCase';
import { ThunkDispatch } from 'redux-thunk';
import StateAction from '../../actions/stateActions';
import SkeletonedLogsListItem from './SkeletonedLogsListItem';
-import '../../styles/log.scss'
+import '../../styles/log.scss';
interface StateProps {
logsCount: number;
@@ -34,18 +34,25 @@ interface DispatchProps {
loadLogs: () => void;
}
-interface Props extends StateProps, DispatchProps {
+interface OwnProps {
isActive: boolean;
}
-function LogsListBase({
+interface Props extends OwnProps, StateProps, DispatchProps {
+}
+
+export interface LogListActions {
+ scrollToTop(): void
+}
+
+const LogsListBase = React.forwardRef<LogListActions, Props>(({
logsCount,
isActive,
loadLogs,
scrolledIndex
-}: Props) {
+}: Props, ref: React.MutableRefObject<LogListActions>) => {
const [loadingLogs, setLoadingLogs] = React.useState(false);
- const virtuoso = React.useRef<Virtuoso>();
+ const virtuosoRef = React.createRef<Virtuoso>();
React.useEffect(() => {
if (isActive && !loadingLogs) {
@@ -56,7 +63,7 @@ function LogsListBase({
React.useEffect(() => {
if (scrolledIndex != null) {
- virtuoso.current?.scrollToIndex(scrolledIndex.valueOf());
+ virtuosoRef.current?.scrollToIndex(scrolledIndex.valueOf());
}
}, [scrolledIndex]);
@@ -64,11 +71,20 @@ function LogsListBase({
return <SkeletonedLogsListItem index={idx} />
};
+ ref.current = {
+ scrollToTop() {
+ if (virtuosoRef.current) {
+ virtuosoRef.current.scrollToIndex(0);
+ }
+ }
+ }
+
+
return (

<Virtuoso
- ref={virtuoso}
+ ref={virtuosoRef}
totalCount={logsCount}
item={renderLog}
overscan={3}
@@ -78,7 +94,7 @@ function LogsListBase({

)
-}
+})
const LogsList = connect(
(state: AppState): StateProps => ({
@@ -89,6 +105,9 @@ const LogsList = connect(
loadLogs: () => dispatch(loadLogs()),
}),
(stateProps, dispatchProps, ownProps) => ({ ...stateProps, ...dispatchProps, ...ownProps }),
-)(LogsListBase);
+ {
+ forwardRef: true
+ }
+)(LogsListBase)
export default LogsList;

diff:
@@ -48,6 +48,10 @@ interface State {
scrolledIndex: Number;
}
+interface OwnProps {
+ ref?: React.MutableRefObject<MessagesCardListBase>
+}
+
export class MessagesCardListBase extends React.PureComponent<Props, State> {
constructor(props: Props) {
@@ -123,8 +127,8 @@ export class MessagesCardListBase extends React.PureComponent<Props, State> {
};
}
-export const MessagesCardList = connect(
- (state: AppState): StateProps => ({
+export const MessagesCardList = connect<StateProps, {}, OwnProps, StateProps & OwnProps>(
+ (state: AppState) => ({
messages: getIsFilterApplied(state) && !state.filter.isTransparent ?
getFilteredMessages(state) :
state.selected.testCase.messages,

original_message:
Bug #63459. Logs panel doesn't scrolled to the top on the panel's button click.

Change-Id: Ib2347ec8b811d636e6f07789a40071a2d22bd60f


message: [RM] Fix timeouts messages.

diff:
@@ -832,7 +832,7 @@ public class WaitAction {
messageBuilder.append("from the checkpoint ");
if (!checkPoint.isSmart()) {
- messageBuilder.append("appx ")
+ messageBuilder.append("appxrox. ")
.append(DateTimeUtility.toLocalDate(checkPoint.getTimestamp()))
.append(" at ")
.append(DateTimeUtility.toLocalTime(checkPoint.getTimestamp()))
@@ -844,7 +844,7 @@ public class WaitAction {
messageBuilder.append("till the timeout is exceeded ");
long currentTime = System.currentTimeMillis();
- messageBuilder.append("appx ")
+ messageBuilder.append("appxrox. ")
.append(DateTimeUtility.toLocalDate(currentTime))
.append(" at ")
.append(DateTimeUtility.toLocalTime(currentTime))


message: [RM] Fix using service settings for codec settings.

diff:
@@ -32,6 +32,7 @@ import org.apache.mina.filter.codec.ProtocolCodecFilter;
import com.exactpro.sf.common.codecs.AbstractCodec;
import com.exactpro.sf.common.codecs.CodecFactory;
import com.exactpro.sf.common.messages.IMessage;
+import com.exactpro.sf.common.util.ICommonSettings;
import com.exactpro.sf.services.AbstractInitiatorService;
import com.exactpro.sf.services.IAcceptorService;
import com.exactpro.sf.services.ISession;
@@ -66,7 +67,7 @@ public abstract class AbstractMINATCPServer extends AbstractInitiatorService imp
throw new ServiceException("Can`t configure server`s acceptor", e);
}
- CodecFactory codecFactory = new CodecFactory(serviceContext, messageFactory, dictionary, getCodecClass(), getSettings());
+ CodecFactory codecFactory = new CodecFactory(serviceContext, messageFactory, dictionary, getCodecClass(), getCodecSettings());
tmpAcceptor.setHandler(this);
tmpAcceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecFactory));
tmpAcceptor.bind(new InetSocketAddress(getSettings().getHost(), getSettings().getPort()));
@@ -75,6 +76,10 @@ public abstract class AbstractMINATCPServer extends AbstractInitiatorService imp
serverSession.set(createServerSession());
}
+ protected ICommonSettings getCodecSettings() {
+ return getSettings();
+ }
+
@Override
public AbstractMINATCPServerSettings getSettings() {
return (AbstractMINATCPServerSettings) settings;

original_message:
[RM] #61731. Fix using service settings for codec settings.

Change-Id: Ia61fc69e78b681ab4a721fa7d1bea765de97e4de


message: [RM] Feature Turn on reuse address for server acceptor.

diff:
@@ -61,6 +61,8 @@ public abstract class AbstractMINATCPServer extends AbstractInitiatorService imp
protected void internalStart() throws IOException {
WrapperNioSocketAcceptor tmpAcceptor = new WrapperNioSocketAcceptor(taskExecutor);
+ tmpAcceptor.setReuseAddress(true);
+
try {
configureAcceptor(tmpAcceptor);
} catch (Exception e) {

original_message:
[RM] Feature #61731. Turn on reuse address for server acceptor.

Change-Id: I6f43f3219a1b88b9657a4472c89403aace9ccbbc


message: Bug Script interruption fix

diff:
******************************************************************************/
package com.exactpro.sf.scriptrunner.impl.jsonreport;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
-
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.ObjectUtils;
-import org.mvel2.math.MathProcessor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.exactpro.sf.SerializeUtil;
import com.exactpro.sf.aml.AMLBlockType;
import com.exactpro.sf.aml.generator.AggregateAlert;
@@ -65,6 +37,7 @@ import com.exactpro.sf.scriptrunner.ReportUtils;
import com.exactpro.sf.scriptrunner.ScriptContext;
import com.exactpro.sf.scriptrunner.ScriptRunException;
import com.exactpro.sf.scriptrunner.StatusDescription;
+import com.exactpro.sf.scriptrunner.StatusType;
import com.exactpro.sf.scriptrunner.TestScriptDescription;
import com.exactpro.sf.scriptrunner.impl.ReportStats;
import com.exactpro.sf.scriptrunner.impl.ReportTable;
@@ -98,6 +71,33 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.collect.Sets;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.mvel2.math.MathProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection, unused, FieldCanBeLocal")
public class JsonReport implements IScriptReport {
@@ -637,6 +637,19 @@ public class JsonReport implements IScriptReport {
public void closeReport() {
assertState(false, null, ContextType.SCRIPT);
+ while (context != null && context.cur != ContextType.SCRIPT) {
+ StatusDescription description = new StatusDescription(StatusType.NA, null);
+ switch (context.cur) {
+ case ACTION:
+ closeAction(description, null);
+ break;
+ case ACTIONGROUP:
+ closeGroup(description);
+ break;
+ case TESTCASE:
+ closeTestCase(description);
+ }
+ }
reportRoot.setFinishTime(Instant.now());
initProperties();
exportToFile(reportRoot, REPORT_ROOT_FILE_NAME, true);

original_message:
Bug #65124. Script interruption fix

Change-Id: I99a12da7cabfde6d9eac7c03aeb41486b81b1714


message: Bug Add information about cause of error into log message

diff:
@@ -19,6 +19,9 @@ import Log from '../../models/Log';
import { createStyleSelector } from '../../helpers/styleCreators';
import SearchableContent from "../search/SearchableContent";
import { keyForLog } from "../../helpers/keys";
+import { RecoverableExpandablePanel } from '../ExpandablePanel';
+import { RecoverableExceptionChain } from '../ExceptionChain';
+import { stopPropagationHandler } from '../../helpers/react';
interface LogCardProps {
log: Log;
@@ -63,13 +66,34 @@ export default function LogCard({ log , index }: LogCardProps){

Exception

{exception.class}

</>
+
}
+

<SearchableContent
contentKey={keyForLog(index, 'message')}
content={message} />
+ {
+ exception &&
+

+ <RecoverableExpandablePanel
+ stateKey={keyForLog(index, 'exception')}>
+ {
+ toggleExpand => (
+

+ Cause
+

+ )
+ }
+ <RecoverableExceptionChain
+ exception={exception}
+ stateKey={`${keyForLog(index, 'exception')}-chain`}/>
+ </RecoverableExpandablePanel>
+

+ }

)

diff:
@@ -24,6 +24,7 @@ import { ThunkDispatch } from 'redux-thunk';
import StateAction from '../../actions/stateActions';
import SkeletonedLogsListItem from './SkeletonedLogsListItem';
import '../../styles/log.scss';
+import StateSaverProvider from '../util/StateSaverProvider';
interface StateProps {
logsCount: number;
@@ -83,6 +84,7 @@ const LogsListBase = React.forwardRef<LogListActions, Props>(({
return (

+ <StateSaverProvider>
<Virtuoso
ref={virtuosoRef}
totalCount={logsCount}
@@ -91,6 +93,7 @@ const LogsListBase = React.forwardRef<LogListActions, Props>(({
style={{height: '100%'}}
className="logs__scroll-container"
/>
+ </StateSaverProvider>

)

diff:
}
}
+.lc-body {
+ display: block;
+ box-sizing: border-box;
+ word-break: break-all;
+
+ &__item-title {
+ font-weight: bold;
+ font-size: 18px;
+
+ &:hover {
+ @include unselectable;
+ cursor: pointer;
+ }
+ }
+
+ &__exception-chain-header {
+ margin-top: 10px;
+ }
+}
+
.lc-skeleton {
@include skeleton-pulse;
box-sizing: border-box;

original_message:
Bug #63789. Add information about cause of error into log message

Change-Id: I5090721b943199aa709a636de610dc8bb050ad4a


message: [RM] Feature Ability to delete the Service logs via GUI

diff:
@@ -23,7 +23,6 @@ import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
-import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
@@ -44,7 +43,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.exactpro.sf.common.util.Pair;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
@@ -214,6 +212,7 @@ public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
/**
* Create file in the last workspace
+ *
* @throws WorkspaceStructureException
*/
@Override
@@ -394,6 +393,7 @@ public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
/**
* Find file in workspaces (search order: form last workspace to deploy folder)
+ *
* @param folderType
* @param fileName
* @return
@@ -406,15 +406,7 @@ public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
for (int i = workspaces.size() - 1; i >= 0; i--) {
WorkspaceLayer layer = workspaces.get(i);
targetFile = new File(layer.get(folderType), toPathString(fileName));
-
- Path targetPath = targetFile.toPath().normalize();
- Path rootPath = workspaces.get(i).get(folderType).toPath().normalize();
- if (! targetPath.startsWith(rootPath)) {
- throw new WorkspaceSecurityException("Access outside workspace is denied. Access file: {" + folderType + "}/" + toPathString(fileName));
- }
-
-
- if (targetFile.exists()) {
+ if (existsOnLayer(folderType, layer, fileName)) {
return Maps.immutableEntry(layer, targetFile);
}
}
@@ -536,6 +528,34 @@ public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
return targetFile;
}
+ @Override
+ public boolean isLastLayerFile(FolderType folderType, String... fileName) throws IOException {
+ checkFolderTypeAndFileName(folderType, fileName);
+
+ if (workspaces.size() <= 1) {
+ return false;
+ }
+
+ try {
+ workspacesLock.readLock().lock();
+ return existsOnLayer(folderType, workspaces.get(workspaces.size() - 1), fileName);
+ } finally {
+ workspacesLock.readLock().unlock();
+ }
+ }
+
+ private boolean existsOnLayer(FolderType folderType, WorkspaceLayer layer, String... fileName) {
+ File targetFile = new File(layer.get(folderType), toPathString(fileName));
+
+ Path targetPath = targetFile.toPath().normalize();
+ Path rootPath = layer.get(folderType).toPath().normalize();
+ if (!targetPath.startsWith(rootPath)) {
+ throw new WorkspaceSecurityException("Access outside workspace is denied. Access file: {" + folderType + "}/" + toPathString(fileName));
+ }
+
+ return targetFile.exists();
+ }
+
private void checkFolderTypeAndFileName(FolderType folderType, String... fileName) {
checkFolderType(folderType);
checkFileName(fileName);

diff:
@@ -167,4 +167,12 @@ public interface IWorkspaceDispatcher {
* @return writeable file from last layer
*/
File getWritableFile(FolderType folderType, String... fileName) throws WorkspaceSecurityException, IOException;
+
+ /**
+ * Checks whether the file is on the last layer. If the last layer is the only one, returns false.
+ * @return
+ */
+ default boolean isLastLayerFile(FolderType folderType, String... fileName) throws IOException {
+ throw new UnsupportedOperationException("Method 'isLastLayerFile' isn't implement.");
+ }
}

diff:
@@ -206,6 +206,7 @@ abstract class AbstractExternalMinaCodecFactory : AbstractExternalCodecFactory()
override fun listFilesAsFiles(filter: FileFilter, folderType: FolderType, recursive: Boolean, vararg fileName: String): Set<File> = throw UnsupportedOperationException()
override fun removeFolder(folderType: FolderType, vararg fileName: String): Unit = throw UnsupportedOperationException()
override fun createFile(folderType: FolderType, overwrite: Boolean, vararg fileName: String): File = throw UnsupportedOperationException()
+ override fun isLastLayerFile(folderType: FolderType, vararg fileName: String): Boolean = throw UnsupportedOperationException()
override fun getOrCreateFile(folderType: FolderType, vararg fileName: String): File = throw UnsupportedOperationException()
override fun getFolder(folderType: FolderType): File = throw UnsupportedOperationException()
override fun getWritableFile(folderType: FolderType, vararg fileName: String): File = throw UnsupportedOperationException()

diff:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
- xmlns:c="http://java.sun.com/jstl/core">
+>
<h:head>
<link rel="shortcut icon" href="#{resource['sf:images/SFicon.ico']}" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
console.error = console.error || function(){};
console.info = console.info || function(){};
+
</script>
</h:head>

+

+

rowKey="#{file.name}" scrollable="true" scrollHeight="100%" resizableColumns="true"
rowStyleClass="eps-download-row #{file.directory ? 'directory-row' : 'file-row'}" styleClass="eps-main-table eps-download-table">
-

-

-

-

-

- <p:ajax event="rowDblselect" listener="#{DownloadBean.toSelected}" update=":form:mainMenuBar :form:curDirP folderContent :form:download-header"
+

+

+

+

+

+

<h:graphicImage library="sf" name="images/download/isDir/#{file.directory}.png"/>

-

+

<h:outputText value="#{file.name}"/>

<h:outputText value="#{file.size}"/>

+

+ <h:outputText value="#{DownloadBean.isDeleteFileAvailable(file) ? '+' : '' }"/>
+

+

+
</h:form>

</h:form>
+ <h:form enctype="multipart/form-data" id="confirmationForm">
+

+

+ <h:outputText value="Are you sure about deleting selected files?"/>
+

+ <h:panelGroup id="deleteFilesList" styleClass="eps-delete-files-list-wrap">
+

+ <ui:repeat value="#{DownloadBean.getTopSelectedFiles(10)}" var="file">
+

+ <h:outputText value="#{file.name}"/>
+

+ </ui:repeat>
+

+ <h:outputText value="... and #{DownloadBean.getNumberOfSelectedFiles() - 10} more files." rendered="#{DownloadBean.getNumberOfSelectedFiles() > 10}"/>
+ </h:panelGroup>
+
+
+

+ <h:outputText value="*Attention! Files can be deleted only from the last layer."/>
+

+
+

+

+

+

+

+ </h:form>
+
</h:body>

\ No newline at end of file

diff:
+/******************************************************************************
+ * Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
+ *
+ * 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.
+ ******************************************************************************/
.highlight {
background-color: #FFFF88;
border-left: 1px solid #cdcdcd;
border-right: 1px solid #cdcdcd;
}
+
+.eps-delete-files-list-wrap {
+ margin: 10px auto;
+}
+
+ul.eps-delete-files-list {
+ height: auto;
+ width: 100%;
+ box-sizing: border-box;
+ margin: 10px auto 0 auto;
+ padding: 4px 30px;
+ list-style: square;
+}
+
+ul.eps-delete-files-list li {
+ height: auto;
+ width: 100%;
+ padding: 2px;
+ margin: 1px 0;
+ border-bottom: 1px solid #eee;
+}
+
+ul.eps-delete-files-list li:last-child {
+ border: none;
+}
\ No newline at end of file

diff:
@@ -21,8 +21,12 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
@@ -303,6 +307,66 @@ public class DownloadBean implements Serializable {
return selectedFiles.length != 1 || selectedFiles[0].isDirectory();
}
+ public boolean isDeleteFileAvailable(FileAdapter fileAdapter) {
+ if (fileAdapter.isDirectory()) {
+ return false;
+ }
+
+ IWorkspaceDispatcher workspaceDispatcher = BeanUtil.findBean(BeanUtil.WORKSPACE_DISPATCHER, IWorkspaceDispatcher.class);
+
+ try {
+ return workspaceDispatcher.isLastLayerFile(FolderType.ROOT, curDirPath, fileAdapter.getFile().getName());
+ } catch (Exception e) {
+ logger.warn("Unable to get information about file {}", fileAdapter.getFile().getName(), e);
+ }
+ return false;
+ }
+
+ public boolean isDeleteFilesNotAvailable() {
+ if (Stream.of(selectedFiles).anyMatch(FileAdapter::isDirectory)) {
+ return true;
+ }
+
+ IWorkspaceDispatcher workspaceDispatcher = BeanUtil.findBean(BeanUtil.WORKSPACE_DISPATCHER, IWorkspaceDispatcher.class);
+
+ return selectedFiles.length == 0 || Stream.of(selectedFiles)
+ .map(FileAdapter::getFile).anyMatch(file ->
+ {
+ try {
+ return !workspaceDispatcher.isLastLayerFile(FolderType.ROOT, curDirPath, file.getName());
+ } catch (Exception e) {
+ logger.warn("Unable to get information about file {}", file.getName(), e);
+ return true;
+ }
+ });
+ }
+
+ public void deleteFiles() throws FileNotFoundException {
+ if (selectedFiles.length > 0) {
+ IWorkspaceDispatcher workspaceDispatcher = BeanUtil.findBean(BeanUtil.WORKSPACE_DISPATCHER, IWorkspaceDispatcher.class);
+
+ for (FileAdapter fa : selectedFiles) {
+ try {
+ workspaceDispatcher.removeFile(FolderType.ROOT, curDirPath, fa.getName());
+ } catch (Exception e) {
+ logger.warn("Unable to remove file {}", fa.getName(), e);
+ }
+ }
+ fillFiles();
+ }
+ }
+
+ public List<FileAdapter> getTopSelectedFiles(int number) {
+ return Arrays.stream(selectedFiles)
+ .limit(number)
+ .sorted(Comparator.comparing(FileAdapter::getName, String.CASE_INSENSITIVE_ORDER))
+ .collect(Collectors.toList());
+ }
+
+ public int getNumberOfSelectedFiles() {
+ return selectedFiles.length;
+ }
+
public void showTextContentModal() {
loadPage("new");

original_message:
[RM] Feature #64908. Ability to delete the Service logs via GUI

Change-Id: Iffd0fd20bcc464bfe334ce87fc843f28f978a40f


message: [RM] Feature JsonYamlDictionaryStructureWriter > fix writing dictionary attributes

change_type: RENAME

diff:
@@ -17,10 +17,13 @@ package com.exactpro.sf.common.impl.messages.json;
import com.exactpro.sf.common.impl.messages.json.configuration.JsonYamlDictionary;
import com.exactpro.sf.common.util.EPSCommonException;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import java.io.File;
import java.io.FileNotFoundException;
@@ -28,16 +31,23 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-public class JsonDictionaryWriter {
+public class JsonYamlDictionaryWriter {
- private static final ObjectMapper objectMapper = new ObjectMapper(
+ private static ObjectMapper objectMapper;
+
+
+ public static void write(JsonYamlDictionary dictionary, OutputStream output, boolean asYaml) {
+
+ if (asYaml) {
+ objectMapper = new ObjectMapper(
new YAMLFactory()
.disable(YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID)
.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID)
- )
- .enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
+ );
+ } else {
+ objectMapper = new ObjectMapper();
+ }
- public static void write(JsonYamlDictionary dictionary, OutputStream output) {
try {
objectMapper.writerFor(JsonYamlDictionary.class).writeValue(output, dictionary);
} catch (IOException exception) {
@@ -45,10 +55,10 @@ public class JsonDictionaryWriter {
}
}
- public static void write(JsonYamlDictionary dictionary, File outputFile) {
+ public static void write(JsonYamlDictionary dictionary, File outputFile, boolean asYaml) {
try (OutputStream outputStream = new FileOutputStream(outputFile)) {
- write(dictionary, outputStream);
+ write(dictionary, outputStream, asYaml);
} catch (IOException e) {
throw new EPSCommonException("Failed to write dictionary:" + dictionary.getName(), e);
}

diff:
@@ -119,6 +119,18 @@ public class JsonYamlDictionary implements IDictionary {
return attributes == null ? Collections.emptyList() : Collections.unmodifiableList(attributes);
}
+ /**
+ * Sets the value of the attribute property
+ *
+ * @param value
+ * allowed object is
+ * {@link List<JsonAttribute>}
+ *
+ */
+ public void setAttributes(List<JsonAttribute> value) {
+ this.attributes = value;
+ }
+
/**
* Gets the value of the fields property.
*

diff:
******************************************************************************/
package com.exactpro.sf.testwebgui.structures;
-import com.exactpro.sf.common.impl.messages.json.JsonDictionaryWriter;
+import com.exactpro.sf.common.impl.messages.json.JsonYamlDictionaryWriter;
import com.exactpro.sf.common.impl.messages.json.configuration.JsonAttribute;
import com.exactpro.sf.common.impl.messages.json.configuration.JsonField;
import com.exactpro.sf.common.impl.messages.json.configuration.JsonMessage;
@@ -33,19 +33,19 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-public class JsonDictionaryStructureWriter {
+public class JsonYamlDictionaryStructureWriter {
- public static void write(ModifiableDictionaryStructure dictionaryStructure, OutputStream output) {
+ public static void write(ModifiableDictionaryStructure dictionaryStructure, OutputStream output, boolean asYaml) {
JsonYamlDictionary dictionary = convertStructureToJson(dictionaryStructure);
- JsonDictionaryWriter.write(dictionary, output);
+ JsonYamlDictionaryWriter.write(dictionary, output, asYaml);
}
- public static void write(ModifiableDictionaryStructure dictionaryStructure, File file) throws IOException {
+ public static void write(ModifiableDictionaryStructure dictionaryStructure, File file, boolean asYaml) throws IOException {
try (OutputStream output = new FileOutputStream(file)) {
- write(dictionaryStructure, output);
+ write(dictionaryStructure, output, asYaml);
} catch (FileNotFoundException e) {
throw new EPSCommonException("Failed to write dictionary: " + dictionaryStructure.getNamespace(), e);
}
@@ -54,9 +54,16 @@ public class JsonDictionaryStructureWriter {
private static JsonYamlDictionary convertStructureToJson(ModifiableDictionaryStructure dictionaryStructure) {
JsonYamlDictionary dictionary = new JsonYamlDictionary(dictionaryStructure.getNamespace());
-
dictionary.setDescription(dictionaryStructure.getDescription());
+ List<JsonAttribute> attributes = new ArrayList<>();
+ if (dictionaryStructure.getAttributes() != null) {
+ for (IAttributeStructure attributeStructure : dictionaryStructure.getAttributes().values()) {
+ attributes.add(createAttributeFromStructure(attributeStructure));
+ }
+ }
+ dictionary.setAttributes(attributes);
+
List<JsonField> fields = new ArrayList<>();
List<JsonMessage> messages = new ArrayList<>();
@@ -173,12 +180,18 @@ public class JsonDictionaryStructureWriter {
private static void addAttributes(ModifiableFieldStructure fieldStructure, JsonField jsonField) {
for (IAttributeStructure attributeStructure : fieldStructure.getImplAttributes()) {
- JsonAttribute jsonAttribute = new JsonAttribute();
- jsonAttribute.setName(attributeStructure.getName());
- jsonAttribute.setValue(attributeStructure.getValue());
- jsonAttribute.setType(attributeStructure.getType());
-
- jsonField.getAttributes().add(jsonAttribute);
+ jsonField.getAttributes().add(createAttributeFromStructure(attributeStructure));
}
}
+
+ private static JsonAttribute createAttributeFromStructure(IAttributeStructure attributeStructure) {
+ JsonAttribute attribute = new JsonAttribute();
+
+ attribute.setName(attributeStructure.getName());
+ attribute.setValue(attributeStructure.getValue());
+ attribute.setType(attributeStructure.getType());
+
+ return attribute;
+
+ }
}

diff:
@@ -22,7 +22,9 @@ import com.exactpro.sf.common.messages.structures.DictionaryComparator.IDiffList
import com.exactpro.sf.common.messages.structures.IDictionaryStructure;
import com.exactpro.sf.common.messages.structures.loaders.JsonYamlDictionaryStructureLoader;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.FileInputStream;
@@ -33,12 +35,14 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-public class JsonDictionaryStructureWriterTest {
+public class JsonYamlDictionaryStructureWriterTest {
private static final Path BASE_DIR = Paths.get((System.getProperty("basedir") == null) ? "." : System.getProperty("basedir")).toAbsolutePath().normalize();
- private static final String FILE_PATH = BASE_DIR + File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + "test_aml.xml";
+ private static final String FILE_PATH = BASE_DIR + File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + "writer-test-dictionary.xml";
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testWriterWithoutDifference() throws IOException {
@@ -51,12 +55,11 @@ public class JsonDictionaryStructureWriterTest {
.load(inputStream);
}
- File tempFile = File.createTempFile("jsonDictionary", ".json");
+ File tempFile = folder.newFile("jsonDictionary.json");
- JsonDictionaryStructureWriter.write(modifiableDictionaryStructure, tempFile);
+ JsonYamlDictionaryStructureWriter.write(modifiableDictionaryStructure, tempFile, false);
IDictionaryStructure dictionaryStructureFromJson;
-
try (InputStream inputStream = new FileInputStream(tempFile)) {
JsonYamlDictionaryStructureLoader loader = new JsonYamlDictionaryStructureLoader();
@@ -82,14 +85,12 @@ public class JsonDictionaryStructureWriterTest {
.load(inputStream);
}
- File tempFile = File.createTempFile("jsonDictionary", ".json");
-
- JsonDictionaryStructureWriter.write(modifiableDictionaryStructure, tempFile);
+ File tempFile = folder.newFile("jsonDictionary.json");
+ JsonYamlDictionaryStructureWriter.write(modifiableDictionaryStructure, tempFile, false);
IDictionaryStructure dictionaryStructureFromJson;
-
try (InputStream inputStream = new FileInputStream(tempFile)) {
JsonYamlDictionaryStructureLoader loader = new JsonYamlDictionaryStructureLoader();

diff:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
<dictionary xmlns="http://exactprosystems.com/dictionary" name="TestAML">
+ <attribute name="Test" type="java.lang.String">TestString</attribute>
<fields>
<field id="F_CHAR" reference="F_TCharacter" name="CHAR"/>
<field id="F_STRING" reference="F_TString" name="STRING"/>

original_message:
[RM] Feature #70790. JsonYamlDictionaryStructureWriter > fix writing dictionary attributes

Change-Id: I17d19f0651f765e6acbada710cdc2c446b8d6a01


message: [RM] Bug Error message correction

diff:
@@ -1394,7 +1394,7 @@ public class NewImpl {
if(dictionary != null && !dictionary.matches(actionDictionary)) {
alertCollector.add(new Alert(action.getLine(), action.getUID(), action.getReference(), fieldName, format(
- "Subaction dictionary '%s' differs from action dictionary '%s'", dictionary, actionDictionary)));
+ "Sub-action dictionary '%s' differs from action dictionary '%s'", dictionary, actionDictionary)));
return;
}
@@ -1408,13 +1408,13 @@ public class NewImpl {
subAction.setMessageTypeColumn(msgType);
} else if(!subMsgType.equals(msgType)) {
alertCollector.add(new Alert(lineNumber, action.getUID(), action.getReference(), fieldName, format(
- "Subaction message type '%s' differs from action message type '%s'", subMsgType, msgType)));
+ "Sub-action message type '%s' differs from action message type '%s'", subMsgType, msgType)));
return;
}
} else {
alertCollector.add(new Alert(lineNumber, action.getUID(), action.getReference(), fieldName, format(
- "Subaction must predefined to use references to it's fields.")));
+ "Sub-action must be predefined to use references to its fields.")));
return;
}
}

diff:
@@ -233,7 +233,7 @@ public class TestAML3_0 extends TestAML3Base {
Assert.assertTrue(alert.toString(), source.remove(alert));
alert = new Alert(15, "", "VWAP", "Reference 'statistics' is not yet defined in column 'VWAP': '${statistics:Turnover}/${statistics:Volume}'.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
- alert = new Alert(15, "", "VWAP", "Subaction must predefined to use references to it's fields.", AlertType.ERROR);
+ alert = new Alert(15, "", "VWAP", "Sub-action must be predefined to use references to its fields.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
alert = new Alert(18, "ss1", "Unable to resolve utility function: random", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
@@ -243,15 +243,15 @@ public class TestAML3_0 extends TestAML3Base {
Assert.assertTrue(alert.toString(), source.remove(alert));
alert = new Alert(21, "ss4", "#static_value", "Invalid URI: com.exactpro.sf.actions.MiscUtils.random", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
- alert = new Alert(27, "ref1", "FInteger", "Subaction must predefined to use references to it's fields.", AlertType.ERROR);
+ alert = new Alert(27, "ref1", "FInteger", "Sub-action must be predefined to use references to its fields.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
alert = new Alert(27, "ref1", "FInteger", "Reference 'rs1' is not yet defined in column 'FInteger': '10 + ${rs1:FInteger}'.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
- alert = new Alert(28, "ref2", "FInteger", "Subaction must predefined to use references to it's fields.", AlertType.ERROR);
+ alert = new Alert(28, "ref2", "FInteger", "Sub-action must be predefined to use references to its fields.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
alert = new Alert(28, "ref2", "FInteger", "Reference 'rs2' is not yet defined in column 'FInteger': '10 + ${rs2:FInteger}'.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
- alert = new Alert(29, "ref3", "FInteger", "Subaction must predefined to use references to it's fields.", AlertType.ERROR);
+ alert = new Alert(29, "ref3", "FInteger", "Sub-action must be predefined to use references to its fields.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));
alert = new Alert(29, "ref3", "FInteger", "Reference 'rs3' is not yet defined in column 'FInteger': '10 + ${rs3:TInteger}'.", AlertType.ERROR);
Assert.assertTrue(alert.toString(), source.remove(alert));

diff:
@@ -201,9 +201,9 @@ public class TestAMLSyntaxNegative extends TestMatrix {
public void testMissingReference() {
logger.info("Start testMissingReference()");
try {
- checkErrorPresents("Found 2 error(s) during preparing script:Error in line 4 column 'ClOrdID': Subaction must predefined to use references to it's fields.", errorsReferenceNameMatrix);
+ checkErrorPresents("Error in line 4 column 'ClOrdID': Sub-action must be predefined to use references to its fields.", errorsReferenceNameMatrix);
- checkErrorPresents("Error in line 4 column 'ClOrdID': Reference 'new_order' is not yet defined in column 'ClOrdID': '${new_order:ClOrdID}'.", errorsReferenceNameMatrix);
+ checkErrorPresents("Found 2 error(s) during preparing script:Error in line 4 column 'ClOrdID': Reference 'new_order' is not yet defined in column 'ClOrdID': '${new_order:ClOrdID}'.", errorsReferenceNameMatrix);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw e;

original_message:
[RM] Bug #70852. Error message correction

Change-Id: Ief1d809d8ea10a4a88fe0900e982ab03f5be0ee3


message: [RM] Feature Add validator's case for field and attribute type compare and field and type attribute length compare

diff:
******************************************************************************/
package com.exactpro.sf.configuration.dictionary.impl;
-import static com.exactpro.sf.common.messages.structures.StructureUtils.getAttributeValue;
-import static com.exactpro.sf.services.MessageHelper.FIELD_MESSAGE_TYPE;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.ATTRIBUTE_MESSAGE_TYPE;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.FIELD_MESSAGE_LENGTH;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.FIELD_START_OF_MESSAGE;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_HEADER;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_HEARTBEAT;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_LOGON;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_REJECT;
-import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_TYPE;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.lang.ArrayUtils;
-
import com.exactpro.sf.common.impl.messages.xml.configuration.JavaType;
import com.exactpro.sf.common.messages.structures.IAttributeStructure;
import com.exactpro.sf.common.messages.structures.IDictionaryStructure;
@@ -48,9 +30,27 @@ import com.exactpro.sf.services.MessageHelper;
import com.exactpro.sf.services.ntg.NTGFieldFormat;
import com.exactpro.sf.services.ntg.NTGProtocolAttribute;
import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
+import org.apache.commons.lang.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static com.exactpro.sf.common.messages.structures.StructureUtils.getAttributeValue;
+import static com.exactpro.sf.services.MessageHelper.FIELD_MESSAGE_TYPE;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.ATTRIBUTE_MESSAGE_TYPE;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.FIELD_MESSAGE_LENGTH;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.FIELD_START_OF_MESSAGE;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_HEADER;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_HEARTBEAT;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_LOGON;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_REJECT;
+import static com.exactpro.sf.services.ntg.NTGMessageHelper.MESSAGE_TYPE;
public class NTGDictionaryValidator extends AbstractDictionaryValidator {
@@ -65,6 +65,18 @@ public class NTGDictionaryValidator extends AbstractDictionaryValidator {
protected static final int lengthLong = 8;
protected static final int[] lengthLocalDateTime = {24};
+ private static final String UINT8 = "UInt8";
+ private static final String UINT16 = "UInt16";
+ private static final String UINT32 = "UInt32";
+ private static final String UINT64 = "UInt64";
+
+ private static final ImmutableBiMap<JavaType, String> POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE = ImmutableBiMap.<JavaType, String>builder()
+ .put(JavaType.JAVA_LANG_SHORT, UINT8)
+ .put(JavaType.JAVA_LANG_INTEGER, UINT16)
+ .put(JavaType.JAVA_LANG_LONG, UINT32)
+ .put(JavaType.JAVA_MATH_BIG_DECIMAL, UINT64)
+ .build();
+
private final Multimap<JavaType, String> typeAllowedFormats = ImmutableSetMultimap.<JavaType, String>builder()
.putAll(JavaType.JAVA_LANG_STRING, NTGFieldFormat.A.name(), NTGFieldFormat.D.name())
.build();
@@ -116,6 +128,10 @@ public class NTGDictionaryValidator extends AbstractDictionaryValidator {
checkFieldOffsets(errors, message, field);
}
+ if (containLength) {
+ checkCompatibilityLengthAndAttributeType(errors, message, field);
+ }
+
if (typeAllowedFormats.containsKey(field.getJavaType())) {
checkFormatAttribute(errors, message, field, typeAllowedFormats.get(field.getJavaType()));
}
@@ -124,6 +140,7 @@ public class NTGDictionaryValidator extends AbstractDictionaryValidator {
return errors;
}
+
private void checkFormatAttribute(List<DictionaryValidationError> errors, IMessageStructure message, IFieldStructure field, Collection<String> allowedFormats) {
if (ValidationHelper.checkRequiredFieldAttribute(errors, message, field, NTGProtocolAttribute.Format.name())) {
String format = getAttributeValue(field, NTGProtocolAttribute.Format.name());
@@ -350,4 +367,89 @@ public class NTGDictionaryValidator extends AbstractDictionaryValidator {
}
}
+ private void checkCompatibilityLengthAndAttributeType(List<DictionaryValidationError> errors, IMessageStructure message, IFieldStructure field) {
+
+ String attributeType = getAttributeValue(field, NTGProtocolAttribute.Type.toString());
+
+ if (attributeType == null) {
+ return;
+ }
+
+ Integer length = getAttributeValue(field, NTGProtocolAttribute.Length.toString());
+
+ switch (attributeType) {
+ case UINT8:
+ checkAttributeTypeSatisfyFieldType(errors, message, field, attributeType);
+
+ if (length == lengthByte) {
+ return;
+ }
+ break;
+
+ case UINT16:
+ checkAttributeTypeSatisfyFieldType(errors, message, field, attributeType);
+
+ if (length == lengthShort) {
+ return;
+ }
+ break;
+
+ case UINT32:
+ checkAttributeTypeSatisfyFieldType(errors, message, field, attributeType);
+
+ if (length == lengthInt) {
+ return;
+ }
+ break;
+
+ case UINT64:
+ checkAttributeTypeSatisfyFieldType(errors, message, field, attributeType);
+
+ if (length == lengthLong) {
+ return;
+ }
+ break;
+
+ default:
+ return;
+
+ }
+ errors.add(
+ new DictionaryValidationError(message == null ? null : message.getName(), field.getName(),
+ String.format("Attribute \"Type\" value [%s] has length different from the attribute \"Length\" value = [%s]",
+ attributeType, length),
+ DictionaryValidationErrorLevel.FIELD, DictionaryValidationErrorType.ERR_ATTRIBUTES));
+ }
+
+ private void checkAttributeTypeSatisfyFieldType(List<DictionaryValidationError> errors, IMessageStructure message,
+ IFieldStructure field, String attributeType) {
+
+ JavaType fieldType = field.getJavaType();
+
+ String possibleType = POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.get(fieldType);
+
+ if (possibleType == null || !possibleType.equals(attributeType)) {
+
+ String errorMessage;
+
+ if (fieldType == JavaType.JAVA_LANG_BYTE) {
+
+ String fieldTypeInStringFormat = field.getJavaType().toString();
+
+ errorMessage = String.format("Attribute \"Type\" value [%s] doesn't match the field type [%s]. Field type [%s] cannot be matched to any unsigned type. Possible value for field type is [%s]",
+ attributeType, fieldTypeInStringFormat, fieldTypeInStringFormat, POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
+
+ } else {
+
+ errorMessage = String.format("Attribute \"Type\" value [%s] doesn't match the field type [%s]. Possible value for attribute type is [%s]. Possible value for field type is [%s]",
+ attributeType, field.getJavaType().toString(), possibleType, POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
+ }
+
+ errors.add(
+ new DictionaryValidationError(message == null ? null : message.getName(), field.getName(),
+ errorMessage,
+ DictionaryValidationErrorLevel.FIELD, DictionaryValidationErrorType.ERR_ATTRIBUTES));
+ }
+ }
+
}

diff:
******************************************************************************/
package com.exactpro.sf.configuration.dictionary;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
import com.exactpro.sf.common.messages.structures.IDictionaryStructure;
import com.exactpro.sf.configuration.dictionary.interfaces.IDictionaryValidator;
import com.exactpro.sf.services.ntg.TestNTGHelper;
import com.exactpro.sf.util.AbstractTest;
-
import org.apache.commons.lang3.ArrayUtils;
-import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
public class TestNTGDictionaryValidator extends AbstractTest {
private final IDictionaryValidator validator = new NTGDictionaryValidatorFactory()
@@ -51,13 +49,19 @@ public class TestNTGDictionaryValidator extends AbstractTest {
List<DictionaryValidationError> errors = validator.validate(dictionary, true, null);
String[] expectedErrors = {
+ "DictionaryValidationError[message=Logon,field=MessageVersion,error=Attribute \"Type\" value [UInt8] doesn't match the field type [JAVA_LANG_BYTE]. Field type [JAVA_LANG_BYTE] cannot be matched to any unsigned type. Possible value for field type is [JAVA_LANG_SHORT],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=Reject,field=RejectCode,error=Attribute \"Type\" value [UInt32] doesn't match the field type [JAVA_LANG_INTEGER]. Possible value for attribute type is [UInt16]. Possible value for field type is [JAVA_LANG_LONG],level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=Reject,field=<null>,error=Message \"Reject\" doesn't contain IsAdmin attribute,level=MESSAGE,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=AnotherTestMessage,field=OrderType,error=Attribute \"Length\" has incorrect value = [8]. Must be one of [1, 2, 4],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=AnotherTestMessage,field=OrderType,error=Attribute \"Type\" value [UInt64] doesn't match the field type [JAVA_LANG_INTEGER]. Possible value for attribute type is [UInt16]. Possible value for field type is [JAVA_MATH_BIG_DECIMAL],level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Length\" has incorrect value = [1]. Must be one of [4, 8],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Type\" value [UInt64] doesn't match the field type [JAVA_LANG_LONG]. Possible value for attribute type is [UInt32]. Possible value for field type is [JAVA_MATH_BIG_DECIMAL],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Type\" value [UInt64] has length different from the attribute \"Length\" value = [1],level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=TestMessage,field=ClOrdID,error=Attribute Format must have one of the next values [A,D] for field with type [java.lang.String] but has [S],level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=TestMessage,field=OrderType,error=Offset attribute is incorrect. actual - 50; expected - 54,level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=<null>,field=<null>,error=Message \"Heartbeat\" is missing in dictionary,level=DICTIONARY,type=ERR_REQUIRED_FIELD]",
"DictionaryValidationError[message=Reject,field=<null>,error=MessageType attribute value is not exist in enum. Value [52],level=MESSAGE,type=ERR_ATTRIBUTES]"
+
};
assertEquals("Wrong errors count", expectedErrors.length, errors.size());

diff:
<field type="java.lang.Byte" name="MessageVersion">
<attribute type="java.lang.Integer" name="Offset">79</attribute>
<attribute type="java.lang.Integer" name="Length">1</attribute>
+ <attribute type="java.lang.String" name="Type">UInt8</attribute>
</field>
</message>
<message name="Reject">
<field type="java.lang.Integer" name="RejectCode">
<attribute type="java.lang.Integer" name="Offset">4</attribute>
<attribute type="java.lang.Integer" name="Length">4</attribute>
+ <attribute type="java.lang.String" name="Type">UInt32</attribute>
</field>
<field type="java.lang.String" name="RejectReason">
<attribute type="java.lang.String" name="Format">A</attribute>
<field defaultvalue="0" type="java.lang.Integer" name="OrderType">
<attribute type="java.lang.Integer" name="Length">8</attribute>
<attribute type="java.lang.Integer" name="Offset">54</attribute>
+ <attribute type="java.lang.String" name="Type">UInt64</attribute>
</field>
<field defaultvalue="1" type="java.lang.Long" name="AnotherOrderType">
<attribute type="java.lang.Integer" name="Length">1</attribute>
<attribute type="java.lang.Integer" name="Offset">62</attribute>
+ <attribute type="java.lang.String" name="Type">UInt64</attribute>
</field>
</message>
</messages>

original_message:
[RM] Feature #71201. Add validator's case for field and attribute type compare and field and type attribute length compare

Change-Id: Ibc88761a4d868bc5f557e4cd114532e0cd289ee5


message: [RM] Bug NTGDictionaryValidator correction

diff:
/******************************************************************************
- * Copyright 2009-2018 Exactpro (Exactpro Systems Limited)
+ * Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import static com.exactpro.sf.common.messages.structures.StructureUtils.getAttributeValue;
import static com.exactpro.sf.services.MessageHelper.FIELD_MESSAGE_TYPE;
@@ -68,7 +69,7 @@ public class NTGDictionaryValidator extends AbstractDictionaryValidator {
private static final String UINT8 = "UInt8";
private static final String UINT16 = "UInt16";
private static final String UINT32 = "UInt32";
- private static final String UINT64 = "UInt64";
+ private static final String UINT64 = "Uint64";
private static final ImmutableBiMap<JavaType, String> POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE = ImmutableBiMap.<JavaType, String>builder()
.put(JavaType.JAVA_LANG_SHORT, UINT8)
@@ -425,31 +426,33 @@ public class NTGDictionaryValidator extends AbstractDictionaryValidator {
IFieldStructure field, String attributeType) {
JavaType fieldType = field.getJavaType();
-
+ String errorMessage = null;
+ if (fieldType == JavaType.JAVA_LANG_INTEGER) {
+ if (!attributeType.equals(UINT8) && !attributeType.equals(UINT16)) {
+ errorMessage = String.format("Use [%s, %s] for Type attribute with current field's type or change field's type to [%s] to use with current Type attribute value",
+ UINT8, UINT16, POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
+ }
+ } else {
String possibleType = POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.get(fieldType);
-
- if (possibleType == null || !possibleType.equals(attributeType)) {
-
- String errorMessage;
+ if (!Objects.equals(possibleType, attributeType)) {
if (fieldType == JavaType.JAVA_LANG_BYTE) {
-
String fieldTypeInStringFormat = field.getJavaType().toString();
-
- errorMessage = String.format("Attribute \"Type\" value [%s] doesn't match the field type [%s]. Field type [%s] cannot be matched to any unsigned type. Possible value for field type is [%s]",
- attributeType, fieldTypeInStringFormat, fieldTypeInStringFormat, POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
+ errorMessage = String.format("Field's type [%s] cannot be matched to any unsigned type. Use [%s] for field's type with current Type attribute",
+ fieldTypeInStringFormat, POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
} else {
-
- errorMessage = String.format("Attribute \"Type\" value [%s] doesn't match the field type [%s]. Possible value for attribute type is [%s]. Possible value for field type is [%s]",
- attributeType, field.getJavaType().toString(), possibleType, POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
+ errorMessage = String.format("Use [%s] for Type attribute with current field's type or change field's type to [%s] to use with current Type attribute value",
+ POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.get(fieldType), POSSIBLE_ATTRIBUTE_TYPE_FOR_FIELD_TYPE.inverse().get(attributeType));
+ }
+ }
}
+ if (errorMessage != null) {
errors.add(
new DictionaryValidationError(message == null ? null : message.getName(), field.getName(),
errorMessage,
DictionaryValidationErrorLevel.FIELD, DictionaryValidationErrorType.ERR_ATTRIBUTES));
}
}
-
}

diff:
/******************************************************************************
- * Copyright 2009-2018 Exactpro (Exactpro Systems Limited)
+ * Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,14 +49,14 @@ public class TestNTGDictionaryValidator extends AbstractTest {
List<DictionaryValidationError> errors = validator.validate(dictionary, true, null);
String[] expectedErrors = {
- "DictionaryValidationError[message=Logon,field=MessageVersion,error=Attribute \"Type\" value [UInt8] doesn't match the field type [JAVA_LANG_BYTE]. Field type [JAVA_LANG_BYTE] cannot be matched to any unsigned type. Possible value for field type is [JAVA_LANG_SHORT],level=FIELD,type=ERR_ATTRIBUTES]",
- "DictionaryValidationError[message=Reject,field=RejectCode,error=Attribute \"Type\" value [UInt32] doesn't match the field type [JAVA_LANG_INTEGER]. Possible value for attribute type is [UInt16]. Possible value for field type is [JAVA_LANG_LONG],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=Logon,field=MessageVersion,error=Field's type [JAVA_LANG_BYTE] cannot be matched to any unsigned type. Use [JAVA_LANG_SHORT] for field's type with current Type attribute,level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=Reject,field=RejectCode,error=Use [UInt8, UInt16] for Type attribute with current field's type or change field's type to [JAVA_LANG_LONG] to use with current Type attribute value,level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=Reject,field=<null>,error=Message \"Reject\" doesn't contain IsAdmin attribute,level=MESSAGE,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=AnotherTestMessage,field=OrderType,error=Attribute \"Length\" has incorrect value = [8]. Must be one of [1, 2, 4],level=FIELD,type=ERR_ATTRIBUTES]",
- "DictionaryValidationError[message=AnotherTestMessage,field=OrderType,error=Attribute \"Type\" value [UInt64] doesn't match the field type [JAVA_LANG_INTEGER]. Possible value for attribute type is [UInt16]. Possible value for field type is [JAVA_MATH_BIG_DECIMAL],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=AnotherTestMessage,field=OrderType,error=Use [UInt8, UInt16] for Type attribute with current field's type or change field's type to [JAVA_MATH_BIG_DECIMAL] to use with current Type attribute value,level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Length\" has incorrect value = [1]. Must be one of [4, 8],level=FIELD,type=ERR_ATTRIBUTES]",
- "DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Type\" value [UInt64] doesn't match the field type [JAVA_LANG_LONG]. Possible value for attribute type is [UInt32]. Possible value for field type is [JAVA_MATH_BIG_DECIMAL],level=FIELD,type=ERR_ATTRIBUTES]",
- "DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Type\" value [UInt64] has length different from the attribute \"Length\" value = [1],level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Use [UInt32] for Type attribute with current field's type or change field's type to [JAVA_MATH_BIG_DECIMAL] to use with current Type attribute value,level=FIELD,type=ERR_ATTRIBUTES]",
+ "DictionaryValidationError[message=AnotherTestMessage,field=AnotherOrderType,error=Attribute \"Type\" value [Uint64] has length different from the attribute \"Length\" value = [1],level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=TestMessage,field=ClOrdID,error=Attribute Format must have one of the next values [A,D] for field with type [java.lang.String] but has [S],level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=TestMessage,field=OrderType,error=Offset attribute is incorrect. actual - 50; expected - 54,level=FIELD,type=ERR_ATTRIBUTES]",
"DictionaryValidationError[message=<null>,field=<null>,error=Message \"Heartbeat\" is missing in dictionary,level=DICTIONARY,type=ERR_REQUIRED_FIELD]",

diff:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ~ Copyright 2009-2018 Exactpro (Exactpro Systems Limited)
+ ~ Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
<field defaultvalue="0" type="java.lang.Integer" name="OrderType">
<attribute type="java.lang.Integer" name="Length">8</attribute>
<attribute type="java.lang.Integer" name="Offset">54</attribute>
- <attribute type="java.lang.String" name="Type">UInt64</attribute>
+ <attribute type="java.lang.String" name="Type">Uint64</attribute>
</field>
<field defaultvalue="1" type="java.lang.Long" name="AnotherOrderType">
<attribute type="java.lang.Integer" name="Length">1</attribute>
<attribute type="java.lang.Integer" name="Offset">62</attribute>
- <attribute type="java.lang.String" name="Type">UInt64</attribute>
+ <attribute type="java.lang.String" name="Type">Uint64</attribute>
</field>
</message>
</messages>

original_message:
[RM] Bug #71201. NTGDictionaryValidator correction

Change-Id: I40df24f602673e08092ee9b6a7a2828b80c0e7d1


message: [RM] Feature Add dictionary attribute test for XmlDictionaryStructureWriter

diff:
/*******************************************************************************
- * Copyright 2009-2019 Exactpro (Exactpro Systems Limited)
+ * Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,10 +67,8 @@ public class XmlDictionaryStructureWriter {
}
}
-
Fields fields = new Fields();
Messages messages = new Messages();
-
// Fields adding
Map<ModifiableFieldStructure, Field> structureFieldMap = new LinkedHashMap<>();
@@ -114,7 +112,6 @@ public class XmlDictionaryStructureWriter {
messageReferenceMap.put(message, (ModifiableMessageStructure)messageStructure.getReference());
}
-
structureMessageMap.put(messageStructure, message);
for(ModifiableFieldStructure fieldStructure : messageStructure.getImplFields().values()) {

change_type: ADD

diff:
+/*******************************************************************************
+ * Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
+ *
+ * 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.
+ ******************************************************************************/
+package com.exactpro.sf.testwebgui.structures;
+
+import com.exactpro.sf.common.impl.messages.xml.configuration.JavaType;
+import com.exactpro.sf.common.messages.structures.IAttributeStructure;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import static com.google.common.collect.Sets.newHashSet;
+
+public class XmlDictionaryStructureWriterTest {
+
+ private static final Path BASE_DIR = Paths.get((System.getProperty("basedir") == null) ? "." : System.getProperty("basedir")).toAbsolutePath().normalize();
+ private static final String FILE_PATH = BASE_DIR + File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + "dictionary_attributes.xml";
+
+ private static final String FIRST_ATTRIBUTE_NAME = "String";
+ private static final String SECOND_ATTRIBUTE_NAME = "Integer";
+ private static final String THIRD_ATTRIBUTE_NAME = "Boolean";
+
+ @Test
+ public void testDictionaryAttributesWriting() throws IOException {
+
+ File dictionary = new File(FILE_PATH);
+
+ ModifiableDictionaryStructure dictionaryStructure = getDictionaryStructure(dictionary);
+
+ ModifiableDictionaryStructure dictionaryStructureAfterWriting;
+ File tempFile = null;
+ try {
+
+ tempFile = File.createTempFile("dictionary", ".xml");
+
+ XmlDictionaryStructureWriter.write(dictionaryStructure, tempFile);
+
+ dictionaryStructureAfterWriting = getDictionaryStructure(tempFile);
+ } finally {
+
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ }
+ Map<String, IAttributeStructure> attributes = dictionaryStructureAfterWriting.getAttributes();
+
+ Assert.assertEquals(newHashSet(FIRST_ATTRIBUTE_NAME, SECOND_ATTRIBUTE_NAME, THIRD_ATTRIBUTE_NAME), attributes.keySet());
+
+ Assert.assertEquals(attributes.get(FIRST_ATTRIBUTE_NAME).getType(), JavaType.JAVA_LANG_STRING);
+ Assert.assertEquals(attributes.get(FIRST_ATTRIBUTE_NAME).getValue(), "AttributeValue");
+
+ Assert.assertEquals(attributes.get(SECOND_ATTRIBUTE_NAME).getType(), JavaType.JAVA_LANG_INTEGER);
+ Assert.assertEquals(Integer.valueOf(attributes.get(SECOND_ATTRIBUTE_NAME).getValue()), Integer.valueOf(5112020));
+
+ Assert.assertEquals(attributes.get(THIRD_ATTRIBUTE_NAME).getType(), JavaType.JAVA_LANG_BOOLEAN);
+ Assert.assertEquals(Boolean.valueOf(attributes.get(THIRD_ATTRIBUTE_NAME).getValue()), true);
+ }
+
+ private ModifiableDictionaryStructure getDictionaryStructure(File dictionary) throws IOException {
+ ModifiableXmlDictionaryStructureLoader loader = new ModifiableXmlDictionaryStructureLoader();
+ try (InputStream inputStream = new FileInputStream(dictionary)) {
+ return loader.load(inputStream);
+ }
+ }
+
+}
\ No newline at end of file

diff:
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Copyright 2009-2020 Exactpro (Exactpro Systems Limited)
+ ~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<dictionary name="DictionaryAttributes" xmlns="http://exactprosystems.com/dictionary">
+ <attribute name="String" type="java.lang.String">AttributeValue</attribute>
+ <attribute name="Integer" type="java.lang.Integer">5112020</attribute>
+ <attribute name="Boolean" type="java.lang.Boolean">True</attribute>
+ <fields>
+ <field name="String" id="String" type="java.lang.String"/>
+ <field name="Integer" id="Integer" type="java.lang.Integer"/>
+ </fields>
+ <messages>
+ <message name="Parent" id="ParentId">
+ <attribute name="AttributeA" type="java.lang.String">ValueA</attribute>
+ <field name="FieldA" reference="String"/>
+ </message>
+ <message name="ChildA" id="ChildAId" reference="ParentId">
+ <attribute name="AttributeB" type="java.lang.String">ValueB</attribute>
+ <field name="FieldB" reference="Integer"/>
+ </message>
+ <message name="ChildB" id="ChildBId" reference="ChildAId">
+ <attribute name="AttributeC" type="java.lang.String">ValueC</attribute>
+ <attribute name="AttributeA" type="java.lang.Long">1</attribute>
+ <field name="FieldC" reference="String"/>
+ <field name="FieldA" reference="Integer"/>
+ <field name="FieldD" reference="ParentId"/>
+ </message>
+ </messages>
+</dictionary>
\ No newline at end of file

original_message:
[RM] Feature #71413. Add dictionary attribute test for XmlDictionaryStructureWriter

Change-Id: Ia54bc9468601e72699e3121774ce4a8b37f63510