-
Notifications
You must be signed in to change notification settings - Fork 23
fix: Fix serialization issues in creation of Vaadin mocks #2082
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
56c69c7
Implement Serializable in ComponentTester class
TatuLund 63863f1
Add test for Vaadin session serialization
TatuLund 2709347
Fix formatting in ComponentTester class declaration
TatuLund 42ec312
Refactor ComponentTester class declaration
TatuLund 76d1bd4
Fix formatting in ComponentTesterTest.java
TatuLund 9f223dc
Add import for VaadinSession in ComponentTesterTest
TatuLund 5694799
Refactor mockVaadinIsSerializable test method
TatuLund df0ef6b
Change uiFactory parameter type to UIFactory
TatuLund db0e386
Update uiFactory parameter to use JvmSerializableLambda
TatuLund 452e682
Refactor uiFactory type in MockSpringVaadinSession
TatuLund 879dd18
Remove unused imports in MockSpringVaadinSession
TatuLund 02ab983
Refactor import statement for UIFactory
TatuLund bfa5b09
Replace Function0 with UIFactory in MockSpringServletService
TatuLund 47ce343
Update constructor parameter type for uiFactory
TatuLund c08b663
Remove unused import for UI component
TatuLund b19ae96
Refactor MockSpringServlet to use UIFactory
TatuLund 4aff22b
Remove unused Kotlin import in MockSpringServlet
TatuLund 19aa423
Update uiFactory to use JvmSerializableLambda
TatuLund 6cc6394
Update ComponentTesterTest.java
TatuLund 336c9bd
Fix duplicate line in mockVaadinIsSerializable test
TatuLund ea57460
Fix MockRequest factory lambda
TatuLund 09c6309
Bring back old constructors as deprecated
TatuLund 251713f
Add serialization debug utility and use it in test
TatuLund 856515c
Clean-up imports
TatuLund 5f78812
Run formatter
TatuLund ef5cad7
Undo un-needed changes, clean-up and move tests to right places.
TatuLund b74e5ea
Mark deprecations for removal
TatuLund 0ed85d7
Merge branch 'main' into tester-serializable
TatuLund 2a6c3b8
Add try - catch
TatuLund 026b0af
Merge branch 'tester-serializable' of https://github.com/vaadin/testb…
TatuLund 03a73d0
Merge branch 'main' into tester-serializable
TatuLund 1d20f08
Improve JavaDoc
TatuLund 0321b56
Merge branch 'tester-serializable' of https://github.com/vaadin/testb…
TatuLund File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
194 changes: 194 additions & 0 deletions
194
...testbench-unit-shared/src/main/java/com/vaadin/testbench/unit/SerializationDebugUtil.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| /** | ||
| * Copyright (C) 2000-2025 Vaadin Ltd | ||
| * | ||
| * This program is available under Vaadin Commercial License and Service Terms. | ||
| * | ||
| * See <https://vaadin.com/commercial-license-and-service-terms> for the full | ||
| * license. | ||
| */ | ||
| package com.vaadin.testbench.unit; | ||
|
|
||
| import java.io.ByteArrayOutputStream; | ||
| import java.io.IOException; | ||
| import java.io.NotSerializableException; | ||
| import java.io.ObjectOutputStream; | ||
| import java.io.Serializable; | ||
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Modifier; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.IdentityHashMap; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public final class SerializationDebugUtil { | ||
|
|
||
| private SerializationDebugUtil() { | ||
| } | ||
|
|
||
| /** | ||
| * Asserts that the given object graph is fully serializable. If not, throws | ||
| * an AssertionError with a detailed report of non-serializable fields | ||
| * found. | ||
| * <p> | ||
| * Note: When running tests in an IDE, enable | ||
| * "sun.io.serialization.extendedDebugInfo" flag for more detailed stack | ||
| * traces on serialization errors. | ||
| * | ||
| * @param root | ||
| * the root object to test for serializability | ||
| * @param report | ||
| * a StringBuilder instance to which the detailed report is | ||
| * appended | ||
| */ | ||
| public static void assertSerializable(Object root, StringBuilder report) { | ||
| try { | ||
| serialize(root); | ||
| } catch (NotSerializableException e) { | ||
| if (report == null) { | ||
| report = new StringBuilder(); | ||
| } | ||
| report.append(buildReport(root, e)); | ||
| throw new AssertionError("Serialization failed: " + e.getMessage() | ||
| + "\n" + report.toString(), e); | ||
| } catch (IOException ioe) { | ||
| throw new AssertionError( | ||
| "Unexpected IO failure during serialization: " | ||
| + ioe.getMessage(), | ||
| ioe); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Asserts that the given object graph is fully serializable. If not, throws | ||
| * an AssertionError with a detailed report of non-serializable fields | ||
| * found. | ||
| * <p> | ||
| * Note: When running tests in an IDE, enable | ||
| * "sun.io.serialization.extendedDebugInfo" flag for more detailed stack | ||
| * traces on serialization errors. | ||
| * | ||
| * @param root | ||
| * the root object to test for serializability | ||
| */ | ||
| public static void assertSerializable(Object root) { | ||
| assertSerializable(root, null); | ||
| } | ||
|
|
||
| private static void serialize(Object o) throws IOException { | ||
| if (o == null) { | ||
| return; | ||
| } | ||
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| ObjectOutputStream oos = new ObjectOutputStream(bos); | ||
| try { | ||
| oos.writeObject(o); | ||
| } finally { | ||
| oos.close(); | ||
| } | ||
| } | ||
|
|
||
| private static String buildReport(Object root, | ||
| NotSerializableException original) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| sb.append("---- Serialization Debug Report ----\n"); | ||
| sb.append("Root type: ").append(root.getClass().getName()).append('\n'); | ||
| sb.append("Original exception: ").append(original).append('\n'); | ||
|
|
||
| Set<Object> visited = Collections | ||
| .newSetFromMap(new IdentityHashMap<>()); | ||
| List<String> offenders = new ArrayList<>(); | ||
| inspectObject(root, "root", visited, offenders); | ||
|
|
||
| if (offenders.isEmpty()) { | ||
| sb.append("No direct non-Serializable fields found.\n"); | ||
| } else { | ||
| sb.append("Non-serializable field paths:\n"); | ||
| offenders.forEach(p -> sb.append(" - ").append(p).append('\n')); | ||
| } | ||
| sb.append("------------------------------------"); | ||
| return sb.toString(); | ||
| } | ||
|
|
||
| private static void inspectObject(Object obj, String path, | ||
| Set<Object> visited, List<String> offenders) { | ||
| if (obj == null || visited.contains(obj)) { | ||
| return; | ||
| } | ||
| visited.add(obj); | ||
|
|
||
| Class<?> cls = obj.getClass(); | ||
|
|
||
| // Skip Java core known immutable serializable types quickly | ||
| if (isKnownSerializableLeaf(cls)) { | ||
| return; | ||
| } | ||
|
|
||
| // If object itself not Serializable, record and do not dive further (to | ||
| // avoid noise) | ||
| if (!(obj instanceof Serializable)) { | ||
| offenders.add(path + " (" + cls.getName() + ")"); | ||
| return; | ||
| } | ||
|
|
||
| // Dive into fields | ||
| for (Field f : getAllFields(cls)) { | ||
| if (shouldSkip(f)) { | ||
| continue; | ||
| } | ||
| try { | ||
| f.setAccessible(true); | ||
| } catch (Exception ignored) { | ||
| continue; | ||
| } | ||
| Object value; | ||
| try { | ||
| value = f.get(obj); | ||
| } catch (IllegalAccessException ignored) { | ||
| continue; | ||
| } | ||
| if (value == null) { | ||
| continue; | ||
| } | ||
|
|
||
| String childPath = path + "." + f.getName(); | ||
| if (!(value instanceof Serializable)) { | ||
| offenders.add( | ||
| childPath + " (" + value.getClass().getName() + ")"); | ||
| continue; | ||
| } | ||
|
|
||
| // Try serializing field alone to catch nested problematic graphs | ||
| try { | ||
| serialize(value); | ||
mcollovati marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } catch (NotSerializableException nse) { | ||
| // Dive deeper to isolate | ||
| inspectObject(value, childPath, visited, offenders); | ||
| } catch (IOException ignored) { | ||
| // Ignore other IO issues for this isolated attempt | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static boolean shouldSkip(Field f) { | ||
| int mod = f.getModifiers(); | ||
| return Modifier.isStatic(mod) || Modifier.isTransient(mod); | ||
| } | ||
|
|
||
| private static List<Field> getAllFields(Class<?> cls) { | ||
| List<Field> fields = new ArrayList<>(); | ||
| while (cls != null && cls != Object.class) { | ||
| fields.addAll(Arrays.asList(cls.getDeclaredFields())); | ||
| cls = cls.getSuperclass(); | ||
| } | ||
| return fields; | ||
| } | ||
|
|
||
| private static boolean isKnownSerializableLeaf(Class<?> cls) { | ||
| return cls.isPrimitive() || cls == String.class | ||
| || Number.class.isAssignableFrom(cls) || cls == Boolean.class | ||
| || cls == Character.class || cls.isEnum() | ||
| || cls.getName().startsWith("java.time."); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.