Skip to content

Commit cc459ee

Browse files
authored
sdk automation, support breakingChangeItems (#42698)
* mock test * implementation * fix breakingChangeItems * fix breakingChangeItems * adapt breaking change * get breaking change items for sdk automation * refactor * nit * nit * fix not changed bug * add test case * extract test method * add necessary line * fix new lines * rename * stage level change * test jar
1 parent 8a8a0f6 commit cc459ee

File tree

11 files changed

+207
-55
lines changed

11 files changed

+207
-55
lines changed

eng/automation/changelog/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@
6565
<artifactId>json</artifactId>
6666
<version>20231013</version>
6767
</dependency>
68+
<dependency>
69+
<groupId>com.azure</groupId>
70+
<artifactId>azure-core-test</artifactId>
71+
<version>1.27.0-beta.2</version> <!-- {x-version-update;com.azure:azure-core-test;dependency} -->
72+
<scope>test</scope>
73+
</dependency>
6874
</dependencies>
6975
<build>
7076
<plugins>

eng/automation/changelog/src/main/java/com/azure/resourcemanager/tools/changelog/Main.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.json.JSONObject;
1515

1616
import java.io.File;
17+
import java.util.ArrayList;
1718
import java.util.List;
1819
import java.util.regex.Pattern;
1920
import java.util.stream.Collectors;
@@ -24,6 +25,12 @@ public class Main {
2425
private final static String NEW_FEATURE_TITLE = "### Features Added\n\n";
2526

2627
public static void main(String[] args) throws Exception {
28+
JSONObject json = getChangelog();
29+
30+
System.out.println(json.toString());
31+
}
32+
33+
static JSONObject getChangelog() {
2734
String oldJar = System.getProperty("OLD_JAR");
2835
if (oldJar == null || oldJar.isEmpty()) {
2936
System.err.println("Cannot found OLD_JAR property");
@@ -62,14 +69,17 @@ public static void main(String[] args) throws Exception {
6269
List<ChangeLog> changeLogs = ChangeLog.fromClasses(classes);
6370
StringBuilder breakingChange = new StringBuilder();
6471
StringBuilder newFeature = new StringBuilder();
72+
List<String> breakingChangeItems = new ArrayList<>();
6573
changeLogs.forEach(x -> {
6674
if (x.isClassLevelChanged()) {
6775
breakingChange.append(x.getBreakingChange());
76+
breakingChangeItems.addAll(x.getBreakingChangeItems());
6877
}
6978
});
7079
changeLogs.forEach(x -> {
7180
if (!x.isClassLevelChanged()) {
7281
breakingChange.append(x.getBreakingChange());
82+
breakingChangeItems.addAll(x.getBreakingChangeItems());
7383
}
7484
});
7585
changeLogs.forEach(x -> {
@@ -87,9 +97,8 @@ public static void main(String[] args) throws Exception {
8797
(newFeature.length() > 0 ? NEW_FEATURE_TITLE + newFeature.toString().replace(namespaces.getBase() + ".", "") : "");
8898

8999
JSONObject json = new JSONObject();
90-
json.put("breaking", breakingChange.length() > 0);
100+
json.put("breakingChanges", breakingChangeItems);
91101
json.put("changelog", changelog);
92-
93-
System.out.println(json.toString());
102+
return json;
94103
}
95104
}

eng/automation/changelog/src/main/java/com/azure/resourcemanager/tools/changelog/changelog/ChangeLog.java

+18-24
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
package com.azure.resourcemanager.tools.changelog.changelog;
55

66
import com.azure.resourcemanager.tools.changelog.utils.AllMethods;
7+
import com.azure.resourcemanager.tools.changelog.utils.BreakingChange;
78
import com.azure.resourcemanager.tools.changelog.utils.ClassName;
89
import com.azure.resourcemanager.tools.changelog.utils.MethodName;
910
import japicmp.model.JApiChangeStatus;
1011
import japicmp.model.JApiClass;
1112
import japicmp.model.JApiMethod;
1213

1314
import java.util.ArrayList;
15+
import java.util.Collection;
1416
import java.util.HashMap;
1517
import java.util.HashSet;
1618
import java.util.Iterator;
@@ -24,20 +26,19 @@
2426
public class ChangeLog {
2527
private AllMethods allMethods;
2628
protected List<String> newFeature;
27-
protected List<String> breakingChange;
28-
29-
ChangeLog() {
30-
this.newFeature = new ArrayList<>();
31-
this.breakingChange = new ArrayList<>();
32-
}
29+
protected BreakingChange breakingChange;
3330

3431
ChangeLog(AllMethods allMethods) {
3532
this.allMethods = allMethods;
3633
this.newFeature = new ArrayList<>();
37-
this.breakingChange = new ArrayList<>();
34+
this.breakingChange = BreakingChange.onJavaClass(getJApiClass().getFullyQualifiedName());
3835
calcChangeLog();
3936
}
4037

38+
protected ChangeLog() {
39+
this.newFeature = new ArrayList<>();
40+
}
41+
4142
public static List<ChangeLog> fromClasses(List<JApiClass> classes) {
4243
Map<String, JApiClass> classMap = classes.stream().collect(Collectors.toMap(JApiClass::getFullyQualifiedName, x -> x));
4344
Map<String, AllMethods> allMethods = new HashMap<>();
@@ -84,14 +85,7 @@ public String getNewFeature() {
8485
}
8586

8687
public String getBreakingChange() {
87-
StringBuilder builder = new StringBuilder();
88-
for (int i = 0; i < this.breakingChange.size(); ++i) {
89-
builder.append(this.breakingChange.get(i)).append("\n");
90-
if (i + 1 == this.breakingChange.size()) {
91-
builder.append("\n");
92-
}
93-
}
94-
return builder.toString();
88+
return this.breakingChange.getForChangelog();
9589
}
9690

9791
public boolean isClassLevelChanged() {
@@ -105,7 +99,6 @@ protected void calcChangeLog() {
10599
}
106100

107101
private void deduplicateChangeLog() {
108-
deduplicate(this.breakingChange);
109102
deduplicate(this.newFeature);
110103
}
111104

@@ -125,15 +118,15 @@ private void deduplicate(List<String> changeList) {
125118
private void calcChangeLogForClass() {
126119
switch (getJApiClass().getChangeStatus()) {
127120
case NEW: newFeature.add(String.format("* `%s` was added", getJApiClass().getFullyQualifiedName())); break;
128-
case REMOVED: breakingChange.add(String.format("* `%s` was removed", getJApiClass().getFullyQualifiedName())); break;
121+
case REMOVED: breakingChange.setClassLevelChangeType(BreakingChange.Type.REMOVED); break;
129122
default:
130123
boolean checkReturnType = !ClassName.name(getJApiClass()).equals("Definition");
131124
allMethods.getMethods().forEach(method -> this.calcChangelogForMethod(method, checkReturnType));
132125
break;
133126
}
134127
}
135128

136-
protected void addClassTitle(List<String> list) {
129+
private void addClassTitle(List<String> list) {
137130
if (list.isEmpty()) {
138131
list.add(String.format("#### `%s` was modified", getJApiClass().getFullyQualifiedName()));
139132
list.add("");
@@ -147,20 +140,21 @@ private void calcChangelogForMethod(JApiMethod method, boolean checkReturnType)
147140
newFeature.add(String.format("* `%s` was added", MethodName.name(method.getNewMethod().get())));
148141
break;
149142
case REMOVED:
150-
addClassTitle(breakingChange);
151-
breakingChange.add(String.format("* `%s` was removed", MethodName.name(method.getOldMethod().get())));
143+
breakingChange.addMethodLevelChange(String.format("`%s` was removed", MethodName.name(method.getOldMethod().get())));
152144
break;
153145
case MODIFIED:
154146
if (!checkReturnType){
155147
if (!method.getOldMethod().get().getLongName().equals(method.getNewMethod().get().getLongName())) {
156-
addClassTitle(breakingChange);
157-
breakingChange.add(String.format("* `%s` -> `%s`", MethodName.name(method.getOldMethod().get()), MethodName.name(method.getNewMethod().get())));
148+
breakingChange.addMethodLevelChange(String.format("`%s` -> `%s`", MethodName.name(method.getOldMethod().get()), MethodName.name(method.getNewMethod().get())));
158149
}
159150
} else {
160-
addClassTitle(breakingChange);
161-
breakingChange.add(String.format("* `%s %s` -> `%s %s`", method.getReturnType().getOldReturnType(), MethodName.name(method.getOldMethod().get()), method.getReturnType().getNewReturnType(), MethodName.name(method.getNewMethod().get())));
151+
breakingChange.addMethodLevelChange(String.format("`%s %s` -> `%s %s`", method.getReturnType().getOldReturnType(), MethodName.name(method.getOldMethod().get()), method.getReturnType().getNewReturnType(), MethodName.name(method.getNewMethod().get())));
162152
}
163153
break;
164154
}
165155
}
156+
157+
public Collection<String> getBreakingChangeItems() {
158+
return breakingChange.getItems();
159+
}
166160
}

eng/automation/changelog/src/main/java/com/azure/resourcemanager/tools/changelog/changelog/DefinitionStageChangeLog.java

+6-16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package com.azure.resourcemanager.tools.changelog.changelog;
55

66
import com.azure.resourcemanager.tools.changelog.utils.AllMethods;
7+
import com.azure.resourcemanager.tools.changelog.utils.BreakingChange;
78
import com.azure.resourcemanager.tools.changelog.utils.ClassName;
89
import com.azure.resourcemanager.tools.changelog.utils.MethodName;
910
import japicmp.model.JApiMethod;
@@ -24,6 +25,7 @@ public class DefinitionStageChangeLog extends ChangeLog {
2425

2526
DefinitionStageChangeLog(Map<String, AllMethods> allStages, String parentClass) {
2627
this.parentClass = parentClass;
28+
this.breakingChange = BreakingChange.onJavaClass(this.parentClass);
2729
oldMethodStages = new ArrayList<>();
2830
newMethodStages = new ArrayList<>();
2931
AllMethods blankStage = allStages.entrySet().stream().filter(x -> ClassName.name(x.getKey()).equals("Blank")).findAny().get().getValue();
@@ -76,34 +78,22 @@ protected void calcChangeLog() {
7678
if (newMethodStages.get(i).contains(method)) {
7779
switch (method.getChangeStatus()) {
7880
case REMOVED:
79-
addClassTitle(breakingChange);
80-
breakingChange.add(String.format("* `%s` was removed in stage %d", MethodName.name(method.getOldMethod().get()), i + 1));
81+
breakingChange.addMethodLevelChange(String.format("`%s` was removed in stage %d", MethodName.name(method.getOldMethod().get()), i + 1));
8182
break;
8283
case MODIFIED:
8384
if (!method.getOldMethod().get().getLongName().equals(method.getNewMethod().get().getLongName())) {
84-
addClassTitle(breakingChange);
85-
breakingChange.add(String.format("* `%s` -> `%s` in stage %d", MethodName.name(method.getOldMethod().get()), MethodName.name(method.getNewMethod().get()), i + 1));
85+
breakingChange.addMethodLevelChange(String.format("`%s` -> `%s` in stage %d", MethodName.name(method.getOldMethod().get()), MethodName.name(method.getNewMethod().get()), i + 1));
8686
}
8787
break;
8888
}
8989
} else if (method.getOldMethod().isPresent()) {
90-
addClassTitle(breakingChange);
91-
breakingChange.add(String.format("* `%s` was removed in stage %d", MethodName.name(method.getOldMethod().get()), i + 1));
90+
breakingChange.addMethodLevelChange(String.format("`%s` was removed in stage %d", MethodName.name(method.getOldMethod().get()), i + 1));
9291
}
9392
}
9493
}
9594
if (newSize > oldSize) {
9695
List<String> newStages = IntStream.range(oldSize + 1, newSize + 1).boxed().map(Object::toString).collect(Collectors.toList());
97-
addClassTitle(breakingChange);
98-
breakingChange.add(String.format("* Stage %s was added", String.join(", ", newStages)));
99-
}
100-
}
101-
102-
@Override
103-
protected void addClassTitle(List<String> list) {
104-
if (list.isEmpty()) {
105-
list.add(String.format("#### `%s` was modified", parentClass));
106-
list.add("");
96+
breakingChange.addStageLevelChange(String.format("Required stage %s was added", String.join(", ", newStages)));
10797
}
10898
}
10999
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.azure.resourcemanager.tools.changelog.utils;
2+
3+
import java.util.Collection;
4+
import java.util.Collections;
5+
import java.util.LinkedHashSet;
6+
import java.util.List;
7+
import java.util.Objects;
8+
import java.util.Set;
9+
import java.util.stream.Collectors;
10+
import java.util.stream.Stream;
11+
12+
/**
13+
* Breaking change information for class.
14+
*/
15+
public class BreakingChange {
16+
17+
private final String className;
18+
private Type type;
19+
private final Set<String> methodChanges = new LinkedHashSet<>();
20+
private final Set<String> stageChanges = new LinkedHashSet<>();
21+
22+
private BreakingChange(String className) {
23+
this.className = className;
24+
setClassLevelChangeType(Type.NOT_CHANGED);
25+
}
26+
27+
public static BreakingChange onJavaClass(String className) {
28+
return new BreakingChange(className);
29+
}
30+
31+
public void setClassLevelChangeType(Type changeType) {
32+
Objects.requireNonNull(changeType);
33+
this.type = changeType;
34+
}
35+
36+
public void addMethodLevelChange(String content) {
37+
setClassLevelChangeType(Type.MODIFIED);
38+
methodChanges.add(content);
39+
}
40+
41+
public void addStageLevelChange(String content) {
42+
setClassLevelChangeType(Type.MODIFIED);
43+
stageChanges.add(content);
44+
}
45+
46+
public String getForChangelog() {
47+
if (type == Type.NOT_CHANGED) {
48+
return "";
49+
}
50+
StringBuilder builder = new StringBuilder();
51+
builder.append(String.format("#### `%s` was %s\n\n", className, type.getDisplayName()));
52+
int count = 0;
53+
List<String> innerChanges = Stream.concat(stageChanges.stream(), methodChanges.stream()).collect(Collectors.toList());
54+
for (String methodChange : innerChanges) {
55+
builder
56+
.append("* ")
57+
.append(methodChange)
58+
.append("\n");
59+
count++;
60+
if (count == innerChanges.size()) {
61+
builder.append("\n");
62+
}
63+
}
64+
return builder.toString();
65+
}
66+
67+
public Collection<String> getItems() {
68+
if (type == Type.NOT_CHANGED) {
69+
return Collections.emptyList();
70+
} else if (methodChanges.isEmpty() && stageChanges.isEmpty()) {
71+
return Collections.singleton(String.format("Class `%s` was %s.", className, type.getDisplayName()));
72+
} else {
73+
return Stream.concat(
74+
stageChanges.stream().map(stageChange -> String.format("%s in class `%s`.", stageChange, className)),
75+
methodChanges.stream().map(methodChange -> String.format("Method %s in class `%s`.", methodChange, className))
76+
).collect(Collectors.toList());
77+
}
78+
}
79+
80+
public enum Type {
81+
NOT_CHANGED("not changed"),
82+
MODIFIED("modified"),
83+
REMOVED("removed"),
84+
;
85+
86+
private final String displayName;
87+
Type(String displayName) {
88+
this.displayName = displayName;
89+
}
90+
91+
public String getDisplayName() {
92+
return displayName;
93+
}
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.azure.resourcemanager.tools.changelog;
2+
3+
import com.azure.core.util.CoreUtils;
4+
import com.azure.resourcemanager.tools.changelog.utils.BreakingChange;
5+
import org.json.JSONArray;
6+
import org.json.JSONObject;
7+
import org.junit.jupiter.api.Assertions;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.net.URL;
11+
12+
public class BreakingChangeTests {
13+
@Test
14+
public void testBreakingChange() {
15+
BreakingChange breakingChange = BreakingChange.onJavaClass("MyClass");
16+
breakingChange.setClassLevelChangeType(BreakingChange.Type.REMOVED);
17+
Assertions.assertEquals("#### `MyClass` was removed\n\n", breakingChange.getForChangelog());
18+
Assertions.assertEquals("Class `MyClass` was removed.", breakingChange.getItems().iterator().next());
19+
20+
String methodLevelContent = "`sku()` was removed";
21+
String methodLevelContent2 = "`tier()` was removed";
22+
breakingChange.addMethodLevelChange(methodLevelContent);
23+
breakingChange.addMethodLevelChange(methodLevelContent2);
24+
// test deduplicate
25+
breakingChange.addMethodLevelChange(methodLevelContent2);
26+
27+
Assertions.assertEquals(2, breakingChange.getItems().size());
28+
Assertions.assertTrue(breakingChange.getForChangelog().contains("#### `MyClass` was modified"));
29+
Assertions.assertEquals("Method `sku()` was removed in class `MyClass`.", breakingChange.getItems().iterator().next());
30+
31+
String stageLevelContent = "Required stage 3 was removed";
32+
breakingChange.addStageLevelChange(stageLevelContent);
33+
34+
Assertions.assertEquals(3, breakingChange.getItems().size());
35+
Assertions.assertEquals("Required stage 3 was removed in class `MyClass`.", breakingChange.getItems().iterator().next());
36+
}
37+
38+
@Test
39+
public void testCompareJars() {
40+
URL oldJar = BreakingChangeTests.class.getResource("/old.jar");
41+
URL newJar = BreakingChangeTests.class.getResource("/new.jar");
42+
System.setProperty("OLD_JAR", oldJar.getFile());
43+
System.setProperty("NEW_JAR", newJar.getFile());
44+
JSONObject jsonObject = Main.getChangelog();
45+
46+
JSONArray breakingChanges = (JSONArray) jsonObject.get("breakingChanges");
47+
String changelog = (String) jsonObject.get("changelog");
48+
Assertions.assertFalse(CoreUtils.isNullOrEmpty(changelog));
49+
Assertions.assertFalse(breakingChanges.isEmpty());
50+
Assertions.assertTrue(breakingChanges.toList().contains("Required stage 3 was added in class `com.azure.resourcemanager.quota.models.CurrentQuotaLimitBase$DefinitionStages`."));
51+
Assertions.assertTrue(breakingChanges.toList().contains("Method `withProperties(com.azure.resourcemanager.quota.models.QuotaProperties)` was removed in stage 2 in class `com.azure.resourcemanager.quota.models.CurrentQuotaLimitBase$DefinitionStages`."));
52+
Assertions.assertTrue(breakingChanges.toList().contains("Method `withProperties(com.azure.resourcemanager.quota.models.QuotaProperties)` was removed in class `com.azure.resourcemanager.quota.models.CurrentQuotaLimitBase$Definition`."));
53+
}
54+
}
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)