Hints on Using Eclipse - pc2ccs/pc2v9 GitHub Wiki

This article contains various notes about using Eclipse for PC2 development. It is not intended to be a tutorial on Eclipse; there are many such articles on the Internet. Rather, these are hints about how to make things easier when using Eclipse to do PC2 development.

Using Git Repositories in Eclipse

PC² code, documents, scripts, and other project elements are stored in Git repositories ("repos" for short). Since the primary development IDE used by the PC2 project developers is Eclipse, it is useful to have tools within Eclipse to manage Git repositories.

(Note that, having said the above, it is not strictly necessary to use Eclipse to manage the PC2 Git repos; Git repo management can be done outside of Eclipse using the Git command line toolset. It's just more convenient in many cases to do it within Eclipse.)

The primary tool for managing Git repos within Eclipse is the EGit Eclipse plugin. See the PC2v9 EGit wiki page for hints on how to manage all your PC2v9 Git functions within Eclipse.

Eclipse Run Configurations

A common approach to testing new PC2 code developed in Eclipse is to open a command prompt (terminal window) and run the code using the appropriate commands (e.g. pc2server, pc2admin, etc.) from the command line. However, after a while it becomes handier to create an Eclipse Run Configuration to do those things for you (albeit inside Eclipse instead of as external processes).

Run Configurations are created by selecting the file you want to run (e.g. edu.csus.ecs.pc2.Starter, which is the main starter class for almost all PC2 modules), right-clicking and selecting Run As > Run Configurations. This allows you to define a new Run Configuration, give it a name and some configuration values, and then use the Run Configuration repeatedly every time you want to run that class.

For example, here are some Eclipse Run Configurations we use all the time:

PC2V9ServerStarter, which is configured to run edu.csus.ecs.pc2.Starter with the arguments --server --login s1 --contestpassword contest (so, starts a Server and automatically logs in to it); This is equivalent to the command line bin/pc2server --login s1 --contestpassword contest

PC2V9ServerSumitHelloStarter, which runs edu.csus.ecs.pc2.Starter with the arguments --server --load sumithello --login s1 --contestpassword contest (so, starts a Server, automatically loads the sample "sumithello" contest so you have a fully-configure contest, and then logs in to the server); This is equivalent to the command line bin/pc2server --load sumithello --login s1 --contestpassword contest

PC2V9AdminStarter, which runs edu.csus.ecs.pc2.Starter with the arguments --login r (so, starts an Admin client, and automatically logs in to it); This is equivalent to the command line bin/pc2admin --login r

PC2V9ScoreboardStarter, which runs edu.csus.ecs.pc2.Starter with the arguments --login b1 (so, starts an Scoreboard client, and automatically logs in to it); This is equivalent to the command line bin/pc2board --login b1

and PC2V9ClientStarter, which runs edu.csus.ecs.pc2.Starter with no program arguments (so, starts a client and uses the credentials you supply through the GUI to log in to it). This is equivalent to the command line bin/pc2team

(Note: all the above Run Configurations also have the following "VM argument": -Djdk.crypto.KeyAgreement.legacyKDF=true; see below.) The command-line scripts automatically apply this argument; when you run from within Eclipse you have to provide it as part of the Run Configuration.)

Ultimately of course, you want to know that the code ALSO works correctly "from the command line the way a user is going to invoke it", so once you've done all the other testing you should always run one last set of tests from the command line...

JUnit Tests

Most classes in PC2 have "JUnit tests" associated with them; these perform "unit testing" to insure the class does what it is supposed to do in a variety of situations. All PC2 JUnit tests are stored under the /test folder, in a subpackage folder of the appropriate name.

JUnit tests should be created for all new classes added to PC2, and all existing JUnit tests should be run when code is changed. To do that, select the test folder, right-click, and select RunAs> This should show you an additional "Run As" option: 3 JUnit Test. Selecting "3 JUnit Test" will run all the JUnits defined in the /test folder.

One trick: some of the JUnit tests are lengthy (they check things like timeout values). You can run just the "Fast" JUnits by creating a JUnit Run Configuration in which you use the "Environment" tab to define an environment variable named pc2fasttest which has the value "any" (literally, any string will work; the variable just has to be defined and have a value). Almost all the PC2 "lengthy" JUnits check for this environment variable and skip themselves if it's defined. This cuts the total test time from something like typically 5 minutes to something like 2 minutes (depending on the machine).

Java Crypto Algorithm and Eclipse Run Configurations

In January 2018, Oracle released a new version of Java (version 8 Build 161) that deprecated a certain Java encryption class (or more specifically, a certain encryption algorithm). However, rather than do what was (and still is) "common practice" by leaving the offending class intact and just pointing people to the improved version (allowing them to choose whether to use it or not), they actually "hid" the algorithm. This irritated a lot of people, especially since Oracle not only included this "fix" in the new 8.161 release of Java but they "back-ported" it to the new release of Java 7. They also included it in the new Java 10 and Java 11 releases.

The issue had to do with the lack of sufficient security in the old cryptographic algorithm; we're not cryptography experts so we don't know the details; we suppose they probably had a justifiable business case for forcing people away from it for "security reasons"...

Bottom line: the result was that a lot of code around the world (including PC2) suddenly stopped working when people did something as simple as upgrade their Java installation.

Fortunately, Oracle at least provided a "work-around": they specified that if you included the argument -Djdk.crypto.KeyAgreement.legacyKDF=true on the JVM (java command) invocation, the code would continue to work with the "old" algorithm. We spent the first three months of 2018 responding to people complaining that "PC2 is broken" (when in fact the issue was that they'd upgraded their Java). We published a "fix" for the issue within about 10 days after it was first reported to us... but it took months to convince people they needed to upgrade their PC2 system to work with their new Java version (or else downgrade their Java)....

Bottom line: if you are using Java 7 after about version 182 or Java 8 after version 161, or any later version of Java, to run a PC2 component, you need to include -Djdk.crypto.KeyAgreement.legacyKDF=true on the java command line. The "fix" we implemented was that we added the -Djdk.crypto.KeyAgreement.legacyKDF=true argument to every script that starts a PC2 module.

However, if you are starting a PC2 module via some means OTHER than using one of the scripts in "./bin" -- for example, using an Eclipse "Run Configuration", you don't automatically get that argument; you have to take steps to explicitly provide it.

In Eclipse, you can do this by adding -Djdk.crypto.KeyAgreement.legacyKDF=true to the VM arguments in the Run Configuration you use to start a module. If you don't include that argument, you'll get one of the "encryption error" messages identified in PC2 Bugzilla under bug reports 1535, 1420, and 1443.

For the case of the PC2 build.xml file (the Ant script which builds a PC2 system in Eclipse, and which is also invoked indirectly by package.xml which builds a complete PC2 distribution), since this is an "external tool" (it's invoking Ant external to Eclipse proper), you have to set the VM argument differently. If you pull down the "Run" menu you'll see "Run Configurations" (those are the Eclipse-internal run configurations) but you'll also see "External Tools > External Tool Configurations". Selecting that will show you a list of what "external run configurations" you have created; one of them should be something like "build.xml". (If not, use the little menu icons on the External Tools Configurations dialog to copy and then modify an existing configuration). Select the "JRE" tab, and enter the -Djdk.crypto.KeyAgreement.legacyKDF=true argument in the "VM arguments" field.

Eclipse JVMs and PC2

tl;dr When testing in eclipse using different JVM versions can cause unexpected "bugs". Ensure that the JVM version configured in eclipse is the same as the system JVM version.

Versions of Java (meaning the Java Virtual Machine, or JVM) are constantly changing. New Java versions frequently introduce nice new features. However, because we want to make PC² as useful as possible to the widest audience possible, we generally avoid using "new" Java features -- because this would require users to upgrade to new versions of Java. Currently for example the minimum "required version" of Java necessary to run PC² is Java 1.8.0 -- which is quite old, but which in our experience a lot of people are still using and are quite happy with. Further, newer versions of Java which users might have installed generally are backward-compatible with Java 1.8.0, so PC² works for them as well.

While this policy helps simplify things for users, it frequently has the opposite effect for PC² developers. First, it requires developers to avoid using new features, even though some such features are powerful, possibly more efficient, and tempting. More importantly, it requires developers to insure that their development environment is consistent with this policy. This can result in some headaches when using tools like Eclipse, because it's easy to "accidentally" upgrade your Eclipse environment in ways that make it incompatible with the policy. Worse, it might be months before you suddenly realize that this has happened.

One example is with the JVM used by Eclipse. The Eclipse Window>Preferences>Java>InstalledJREs dialog will show what Java Runtime Environment (JRE) is currently being used for the Eclipse Project's build path. Typically, developers expect that the JVM will be inherited from their system via the PATH variable. However, this is not always true. Specifically, when Eclipse is launched, it reads a file named eclipse.ini from the directory where the eclipse.exe executable is started. Eclipse looks in this file for a pair of lines of the form

-vm
<path_to_jvm>

If these lines are not present, Eclipse uses other mechanisms (e.g. looking at the PATH variable inherited from the underlying system environment) to determine what JVM it should use. However, if these lines are present, Eclipse ignores any JVM specification in the PATH environment variable and instead uses the path specified on the second line (after -vm) to determine what JVM (JRE) it should use. Note in particular that this means that Eclipse will use a JVM which is different from the one selected in the Window>Preferences>Java>InstalledJREs dialog!

This can become a problem because some automatic updates can modify the eclipse.ini file, adding an unexpected -vm entry -- with the result being that the developer is using a different JVM than what is shown as "selected" in the Window>Preferences>Java>InstallJREs dialog. This can produce some weird side-effects which are hard to track down.

For example, one instance of this happening produced the following symptoms:

  • The selected Project JRE in Eclipse was jre1.8.0_201.
  • Compiling and executing the "hello.java" program found under the PC2 samps\src folder using command-line operations (i.e. outside of Eclipse) was successful (the program compiled and ran correctly).
  • Submitting that exact same source code to a PC2 system run from a packaged distribution which was built using Eclipse was successful -- a Team Client was able to get a "Yes" result from an AutoJudge by submitting the sample source file.
  • Submitting that exact same source code to a PC2 system run from within Eclipse produced a Run-Time Error (RTE) response from the AutoJudge.

The problem turned out to be that Eclipse was internally compiling the code using Java 15 (due to an unsuspected upgrade to the eclipse.ini file as described above) -- but then PC2 was attempting to execute the code using an external command-line call like java hello, which used the (external) environment PATH variable to invoke Java 1.8.0_201. The result was that the Java 1.8 JVM did not recognize the Java 15 .class file, and generated a exception (hence, the RTE).

Bottom line: it is important for developers to insure that their Eclipse environment is properly set up...

(Additional side note: the above problem was particularly hard to diagnose because the error message generated by the failure of the Java 1.8 JVM to be able to process the Java 15 .class file gets written to stderr. However, there is no place in the (current) PC² system where a Judge can see the stderr output of a program. The only place where this error appears is on a Fat Team Client during a Test Run (this is how the error was ultimately found. And yes, a Bug Report/Enhancement request for displaying stderr to Judges has subsequently been filed...)

Using REST Assured

REST Assured is a Java library that provides a domain-specific language (DSL) for writing tests for RESTful APIs. Under certain conditions, a SecurityException can be thrown when running a REST Assured unit test in PC² under Eclipse. Specifically, an exception may be thrown on a call to method equalsTo().

Solution: Change the order of the .jars. Move the JUnit .jar below the REST Assured and Hamcrest jars in the classpath.

From .classpath:
<classpathentry kind="lib" path="lib/rest-assured-3.0.3.jar"/> <classpathentry kind="lib" path="lib/hamcrest-library-1.3.jar"/>
<classpathentry kind="lib" path="lib/hamcrest-core-1.3.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="lib" path="lib/commons-codec-1.9.jar"/>

Cause: the JUnit jar has Hamcrest methods/package that are used instead of the the REST Assured and Hamcrest jar.

Details:

java.lang.SecurityException: class "org.hamcrest.Matchers"'s signer information does not match signer information of other classes in the same package
at java.lang.ClassLoader.checkCerts(ClassLoader.java:895)
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:665)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)

Example JUnit code with equalsTo call

For a rest assured equalTo test there may be a given(). contentType(ContentType.JSON). body(jsonAsMap). log().all(). when().post(loginUrl). then().statusCode(200). body("contest.name", equalTo("First Contest"));

Solution found here: https://stackoverflow.com/questions/9651784/hamcrest-tests-always-fail

⚠️ **GitHub.com Fallback** ⚠️