-
Notifications
You must be signed in to change notification settings - Fork 85
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
Add plugin support #140
Add plugin support #140
Conversation
Moved a bunch of code into :api Not all tests succeed
api/api.gradle.kts
Outdated
fun testFx(name: String, version: String = "4.0.+") = | ||
create(group = "org.testfx", name = name, version = version) | ||
testCompile(testFx(name = "testfx-core")) | ||
testCompile(testFx(name = "testfx-junit5")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this testCompile
dependency is shared now in both api
and app
you should put it there so you don't have duplicate version numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"There" being...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build.gradle.kts
in the root project.
return name; | ||
} | ||
|
||
public final Class<T> getJavaClass() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vs getting the Groovy class or the Kotlin class? What is this supposed to mean? A javadoc would be helpful.
return DataTypes.forName(name).orElse(DataTypes.Unknown); | ||
@Override | ||
public int hashCode() { | ||
return getClass().hashCode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't you also hashing on the name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite a few things.
@Override | ||
public boolean equals(Object obj) { | ||
if (obj == null) { | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually, you have a check for this immediately after the null check as an optimization:
if (this == obj) return true;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should really stop using IntelliJ's autogenerated equals/hashcode methods, they miss stuff like this
}); | ||
} | ||
|
||
private static Comparator<Class<?>> closestTo(Class<?> target) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why this is needed and what it is? Also, this is a complex bit of logic, are you sure that Guava doesn't have some sort of reflection utility to do this?
https://github.com/google/guava/wiki/ReflectionExplained
Also, if you do need it, I really hope that this is unit tested.
.collect(Collectors.toSet()); | ||
} | ||
|
||
private static Class<?> boxedType(Class<?> primitiveType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be replaced with Primitives.wrap
:
System.out.println("GENERATING FOR " + plugin.getName()); | ||
setup(plugin); | ||
} else { | ||
System.out.println("REMOVING ITEMS FOR " + plugin.getName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want to move to using a logger like slf4j
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left those in while I was debugging. We use Java's builtin Logger
class for logging; this was a quick and dirty (and easy to read) way to debug
@@ -213,7 +271,7 @@ public void save() { | |||
private void saveAs() { | |||
FileChooser chooser = new FileChooser(); | |||
chooser.getExtensionFilters().setAll( | |||
new FileChooser.ExtensionFilter("SmartDashboard Save File (.json)", "*.json")); | |||
new FileChooser.ExtensionFilter("SmartDashboard Save File (.json)", "*.json")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatter changes?
return false; | ||
} | ||
} | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Log in this case too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'll be logged a lot; one statement per non-plugin class. For example, the base
plugin jar would log ~20 times here
@@ -19,11 +19,12 @@ | |||
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals; | |||
|
|||
@Disabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this whole test disabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dependent on network tables
@@ -0,0 +1,3 @@ | |||
dependencies { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above this line add a description = "[SOME HELPFUL DESCRIPTION HERE]"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been resolved.
plugins/base/base.gradle.kts
Outdated
""".trimMargin() | ||
|
||
dependencies { | ||
foo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wat?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, was trying to see why it wasn't evaluating declarations like compile
and testRuntime
plugins/plugins.gradle.kts
Outdated
create(group = "org.testfx", name = name, version = version) | ||
testCompile(testFx(name = "testfx-core")) | ||
testCompile(testFx(name = "testfx-junit5")) | ||
testRuntime(testFx(name = "openjfx-monocle", version = "8u76-b04")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is again duplicated here.
A widget is disabled when: - the plugin defining that widget is unloaded - the plugin defining the source for that widget is unloaded - the plugin defining the data type of that widget is unloaded
.filter(t -> t != All) | ||
.sorted((t1, t2) -> classComparator.reversed().compare(t1.getJavaClass(), t2.getJavaClass())) | ||
.collect(Collectors.toList()); | ||
return Optional.of(sorted.get(0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you just call first()
on the set and get an optional?
Also, should this be Optional.ofNullable
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that may work. ofNullable
is pointless since elements should never be null
*/ | ||
public final void registerAll(T... items) { | ||
Objects.requireNonNull(items, "items"); | ||
for (T item : items) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The array must not be null but you could pass registerAll(item1, null, item2, null)
what would happen in that case? Should you check each of the args for null too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
register
does a null check....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay! Cool!
* @throws InvalidWidgetException if the widget is invalid | ||
*/ | ||
private static void validate(Description description) throws InvalidWidgetException { | ||
private void validate(Description description) throws InvalidWidgetException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this no longer static? It doesn't use any member variables.
Never mind
*/ | ||
public List<Widget> getActiveWidgets() { | ||
// Use a copy; don't want elements in the list to be removed by GC while someone's using it | ||
return new ArrayList<>(activeWidgets.keySet()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use an ImmutableList.copyOf
?
|
||
expected = new String[]{"foo", "bar"}; | ||
assertArrayEquals(expected, Serialization.readStringArray(fooBarBytes, 0)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why were all these tests removed? Are they now irrelevant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They were dependent on serializers that were moved to the base plugin
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay.
public void loadPluginJar(URI jarUri) throws IOException { | ||
URL url = jarUri.toURL(); | ||
PrivilegedAction<URLClassLoader> getClassLoader = () -> { | ||
return new URLClassLoader(new URL[]{url}, ClassLoader.getSystemClassLoader()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this using the class loader that the rest of the code is using?
I know there are some wacky edge cases you need to be aware of when dealing with class loaders and the garbage collector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd have to look into that. This is basically the same code that the original dashboard uses for loading jars at runtime and that has worked for the past 6+ years
plugin.getThemes().forEach(Themes.getDefault()::register); | ||
|
||
plugin.onLoad(); | ||
plugin.setLoaded(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary?
|
||
/** | ||
* A type of data source that represents the state of another source that has been destroyed or removed as a result | ||
* of its defining plugin being unloaded. The data source may |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete javadoc.
@@ -19,11 +19,12 @@ | |||
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals; | |||
|
|||
@Disabled("Depends on network tables") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a plan to re-enable this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I'll need to change the save file to use mock widgets
@Test | ||
public void testLoadClass() { | ||
PluginLoader loader = new PluginLoader(); | ||
boolean loaded = loader.loadPluginClass(MockPlugin.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As soon as you try doing this with more complicated classes that actually have plugin logic you are going to end up adding stuff to the Widgets
singleton and other singletons which will affect other tests.
@AfterAll | ||
public static void resetRegistries() { | ||
Widgets.setDefault(new Widgets()); | ||
DataTypes.setDefault(new DataTypes()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where static singletons bite you in the butt. You really shouldn't need to do this.
If you have a failure in one of your tests here it can cause a ripple throughout the rest of your testing framework which can be really hard to debug. I would highly recommend you refactor your code so that you can test it without using static singletons.
Codecov Report
@@ Coverage Diff @@
## master #140 +/- ##
=============================================
- Coverage 42.6% 13.22% -29.38%
+ Complexity 475 186 -289
=============================================
Files 125 136 +11
Lines 3070 3523 +453
Branches 236 263 +27
=============================================
- Hits 1308 466 -842
- Misses 1667 3026 +1359
+ Partials 95 31 -64 |
Moves builtin widgets and data types to
:plugins:base
Moves network table sources and widgets to
:plugins:networktables
Autogenerates source trees for each registered source type
Features
SourceType
; nothing is hard-coded anymore in:api
or:app
Known issues
Screenshots
Plugin preferences dialog