From 99aad5164c89c2ccc3e2c70fb329b1d8a3313458 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Wed, 14 Jun 2023 12:27:45 +0300 Subject: [PATCH] fix: AbstractTestElement#clone might produce non-identical clones if element constructor adds a non-default property value Historically AbstractTestElement calls default constructor in order to create a clone. It might be that a constructor would add properties (e.g. LoopController, HeaderManager), so we might get invalid clones if the clone source _removes_ a property which was added in the constructor. --- .../testelement/AbstractTestElement.java | 9 ++++ .../testelement/AbstractTestElementTest.kt | 53 +++++++++++++++++++ xdocs/changes.xml | 1 + 3 files changed, 63 insertions(+) create mode 100644 src/core/src/test/kotlin/org/apache/jmeter/testelement/AbstractTestElementTest.kt diff --git a/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java b/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java index 6bbf3bad48d..ff160dfc13e 100644 --- a/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java @@ -121,6 +121,15 @@ public Object clone() { try { TestElement clonedElement = this.getClass().getDeclaredConstructor().newInstance(); + // Default constructor might be configuring non-default properties, and we want + // the clone to be identical to the source, so we remove properties before copying. + // For example, LoopController in JMeter 5.5 + // Note: clonedElement.clear(); might set unwanted options as well. + PropertyIterator clonedProps = clonedElement.propertyIterator(); + while (clonedProps.hasNext()) { + clonedProps.next(); + clonedProps.remove(); + } PropertyIterator iter = propertyIterator(); while (iter.hasNext()) { clonedElement.setProperty(iter.next().clone()); diff --git a/src/core/src/test/kotlin/org/apache/jmeter/testelement/AbstractTestElementTest.kt b/src/core/src/test/kotlin/org/apache/jmeter/testelement/AbstractTestElementTest.kt new file mode 100644 index 00000000000..10347c23a2a --- /dev/null +++ b/src/core/src/test/kotlin/org/apache/jmeter/testelement/AbstractTestElementTest.kt @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 org.apache.jmeter.testelement + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class AbstractTestElementTest { + @Test + fun `clone can remove properties`() { + class ElementWithDefaultComment : AbstractTestElement() { + init { + set(TestElementSchema.comments, "initialized in constructor") + } + } + + val source = ElementWithDefaultComment().apply { + removeProperty(TestElementSchema.comments.name) + } + + val cloned = source.clone() as ElementWithDefaultComment + + cloned.propertyIterator().asSequence().toList() + + assertEquals( + source.propertyIterator().asSequence().toList().joinToString("\n") { it.name + " = " + it.stringValue }, + cloned.propertyIterator().asSequence().toList().joinToString("\n") { it.name + " = " + it.stringValue }, + ) { + "The properties after cloning the element should match the original properites. " + + "Note that comments is added in constructor, however, we remove <> property before cloning" + } + + assertEquals(source, cloned) { + "The cloned element should be be equal the original element. " + + "Note that comments is added in constructor, however, we remove <> property before cloning" + } + } +} diff --git a/xdocs/changes.xml b/xdocs/changes.xml index 4a295d713f7..8d169f75e4a 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -230,6 +230,7 @@ Summary
  • 58725874Trim name in Argument objects.
  • 693Avoid wrong results when Object.hashCode() happen to collide. Use IdentityHashMap instead of HashMap when key is TestElement
  • Refresh UI when dragging JMeter window from one monitor to another, so rich syntax text areas are properly editable after window movement
  • +
  • 5984AbstractTestElement#clone might produce non-identical clones if element constructor adds a non-default property value