Forums

Search results: 40

Question Authors' Forum -> Java packages -> Re: Java packages

by Peter Sander -

Moving right along...

I also noticing from letting students loose on CodeRunner led to occasional unfortunate behaviour. For example, having given them code specs where they were to use enums in a switch, they might be tempted to just go with if-then-else-if-then-else... To CodeRunner's tester the output looks like what's wanted and the test case passes. Not what I wanted.

So I've cobbled up a few code-checker modules to actually do some static code analysis (extremely basic, it's not FindBugs but the students don't know that  :^)  ) to check whether their code is vaguely in-spec. If the code is out-of-spec CodeRunner pops an informative exception and they get to try again.

This works along the same principles as my last post - there's a module file java_code_checker.py included in the LOCAL_PROTOTYPE_java_checker question prototype which does the work of code-analysis.

For the enum checker, the template looks like:

from java_code_checker import *
from java_code_tester import *

student_answer = """{{ STUDENT_ANSWER | e('py') }}"""

student_answer = assemble_student_answer(student_answer, '{{QUESTION.parameters.enum}}')

# checks if enum used
check_for_enum(student_answer, '{{QUESTION.parameters.enum}}')

# loads and filters support files
support_files = assemble_support_files()

# assembles and saves test file from student answer and support files
assemble_tester(student_answer, support_files, '''{{TEST.testcode}}''')

# take it for a spin
compile_and_run()

These checks need a bit more configuration via the template parameters. For example, for the enums example I need to configure the name of the enum via

{"enum": "DayOfWeekend"}
because I need the name of the enum in order to include the possibility of a static import of its identifiers. One day I'll regex that bit of cruft away. One day...

This is nowhere near serious static code analysis, but it seems to keep the students a bit more honest.

Question Authors' Forum -> Java packages -> Re: Java packages

by Peter Sander -

Right, now that I've been using CodeRunner in anger in a Java-based course, I've had another go at making it workable. Instead of customizing templates, which quickly became unmanageable, I've put the code into modules which are then loaded as support files. This reduces the amount of customization of templates to an import statement and a couple of function invocations. For example, to test student answer functionality the customized template becomes:

from java_code_tester import *

student_answer = """{{ STUDENT_ANSWER | e('py') }}"""

# manipulates student answer
student_answer = assemble_student_answer(student_answer)

# loads and filters support files
support_files = assemble_support_files()

# assembles and saves test file from student answer and support files
assemble_tester(student_answer, support_files, '''{{TEST.testcode}}''')

# take it for a spin
compile_and_run()

I've worked this into a LOCAL_PROTOTYPE_java_tester to use as a question prototype and to be customize further as needed. Pre-loading the module file java_code_tester.py into the prototype as a support file means not having to remember to load it into every question; it's already available.

The java_code_tester.py file is attached below.

Question Authors' Forum -> Java packages -> Re: Java packages

by Peter Sander -

I've tweaked my java-in-a-packagetemplate to permit package names including a dot. So, eg, the following should work for the class foo.bar.Toto:

import sys, os, shutil, subprocess
__student_answer__ = """{{ STUDENT_ANSWER | e('py') }}"""
ncoding = '{{ QUESTION.parameters.encoding }}' or 'utf-8'
# Make package directory, put support files into it
package_dir = '{{ QUESTION.parameters.package }}'.replace('.', '/')
os.makedirs(package_dir)
[shutil.move(f, package_dir) for f in os.listdir() if f.endswith('.java')]
# Put student answer class into package directory
with open(package_dir + '/{{ QUESTION.parameters.class}}.java', mode='w', encoding=ncoding) as fd:
    print(__student_answer__, file=fd)
    fd.close()
    
# Build test class in package directory too
tester = '''package {{ QUESTION.parameters.package }};
public class __Tester__ {
    public static void main(String[] args) {
        {{TEST.testcode}}
    }
}
'''
with open(package_dir + '/__Tester__.java', mode='w', encoding=ncoding) as fd:
    print(tester, file=fd)
    fd.close()
# Compile and run
if (subprocess.call(['javac', '-encoding', ncoding, package_dir + '/__Tester__.java']) != 0 or
   subprocess.call(['java', package_dir + '/__Tester__']) != 0):
    print('** Further testing aborted **', file=sys.stderr)

Question Authors' Forum -> Java packages -> Re: Java packages

by Peter Sander -

Ok, here's a slightly modified template to make it more generic. I've left in two template parameters for the name of the package and the student answer class, eg, {"package": "foobar", "class": "Foo"}. Basically I bailed on extracting this information from the student answer by trying to anticipate how students might construct their Java code file. Maybe later.

There's a limitation in that the student answer and supporting files must all be in the same package

import sys, os, shutil, subprocess
__student_answer__ = """{{ STUDENT_ANSWER | e('py') }}"""
# Make package directory, put support files into it
os.mkdir("{{ QUESTION.parameters.package }}")
[shutil.move(f, "{{ QUESTION.parameters.package }}") for f in os.listdir() if f.endswith(".java")]
# Put student answer class into package directory
with open("{{ QUESTION.parameters.package }}/{{ QUESTION.parameters.class}}.java", "w") as f:
    print(__student_answer__, file=f)
    
# Build test class in package directory too
tester = """package {{ QUESTION.parameters.package }};
public class __Tester__ {
    public static void main(String[] args) {
        {{TEST.testcode}}
    }
}
"""
with open("{{ QUESTION.parameters.package }}/__Tester__.java", "w") as f:
    print(tester, file=f)
# Compile and run
if (subprocess.call(["javac", "{{ QUESTION.parameters.package }}/__Tester__.java"]) != 0 or
   subprocess.call(["java", "{{ QUESTION.parameters.package }}/__Tester__"]) != 0):
    print("** Further testing aborted **", file=sys.stderr)

Question Authors' Forum -> Java packages -> Re: Java packages

by Richard Lobb -

Java packages are somewhat incompatible with the usual CodeRunner/Jobe model of a single directory in which the Twig output file plus any support files are placed prior to execution. To get around this you need to build the required directory hierarchy yourself. [And if you thought your solution was awkward, I'm not sure what you'll think of mine!] I always script tricky stuff like this in Python. Here's a solution that works for your particular problem:

  1. In Advanced customisation, set the main language to Python3, the Ace language to Java and set the memory limit to 0 (as the Java VM tries to reserve ludicrous amounts of memory).
  2. Set the test code to
    Foo foo = new Foo();
    System.out.println(foo);
    
  3. Load Bar.java as a support file
  4. Set the template to the following:
    import sys, os, shutil, subprocess
    
    __student_answer__ = """{{ STUDENT_ANSWER | e('py') }}"""
    
    # Make foobar directory, put Bar.java and Foo.java into it
    os.mkdir('foobar')
    shutil.move("Bar.java", "foobar/")
    with open("foobar/Foo.java", "w") as f:
        print(__student_answer__, file=f)
        
    # Build test class in foobar directory too
    tester = """package foobar;
    public class __Tester__ {
        public static void main(String[] args) {
            {{TEST.testcode}}
        }
    }
    """
    with open("foobar/__Tester__.java", "w") as f:
        print(tester, file=f)
    
    # Compile and run
    if (subprocess.call(["javac", "foobar/__Tester__.java"]) != 0 or
       subprocess.call(["java", "foobar/__Tester__"]) != 0):
        print("** Further testing aborted **", file=sys.stderr)
    
Proof it works:


Although that's pretty complex, if you have many questions involving packages, you could abstract this into a separate question type in which the support files specify the required package hierarchy (e.g. you might have a zip archive to be unzipped plus an additional config file).

One shortcoming worth noting: CodeRunner classifies any output to stderr as a "Runtime error" and aborts the running of further testcases. Thus compilation errors in the student's code are tagged as "Runtime errors", although the usual compiler diagnostic messages still appear in the output. You could prevent this misclassification by catching stderr output from the subprocess.call but you would then continue to run all other test cases, generating the same compile error output. There's no easy way round this, though you could use a template grader if it really worried you. 

Question Authors' Forum -> Java packages

by Peter Sander -

Hello,

I'm having trouble getting the following to work.

I have a java_class question for students to develop. My sample answer looks like this:

package foobar;
public class Foo {
    private Bar bar;
    public String toString() {return "whatever";}
}

The class Foo is in a support file called Bar.java containing:

package foobar;
public class Bar {}

Not very exciting I'll admit.

In the question preview with the above sample answer submitted as the student answer, Check gives:

Debug: source code from all test runs

Run 1

package foobar;

class Foo {
    private Bar bar;
    public String toString() {
        return "whatever";
    }
}

public class __Tester__ {

    public static void main(String[] args) {
        __Tester__ main = new __Tester__();
        main.runTests();
    }

    public void runTests() {
        System.out.println(new Foo());
    }
}

which doesn't look unreasonable. However, the compiler seems unable to find the class Bar even though it's declared in the same package.

Syntax Error(s)

__Tester__.java:4: error: cannot find symbol
    private Bar bar;
            ^
  symbol:   class Bar
  location: class Foo
1 error

Take out the package declarations and the code works as expected. I've tried diddling the template, eg. putting a package declaration at the top, with no great success. 

Take out the variable declaration Bar bar and removing the support file makes the class compile, but Check now gives a runtime error:

 

TestExpectedGot
IncorrectSystem.out.println(new Foo())
whatever
***Runtime error***
Exception in thread "main" java.lang.NoClassDefFoundError: __Tester__ (wrong name: foobar/__Tester__)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)

Has anyone gotten questions with Java classes in packages to work?

I should mention that I can strip out the package declaration from the student answer with

{{ STUDENT_ANSWER | replace({'package foobar;': ''}) | replace({'public class ': 'class ' }) }}

in the template, but that just seems awkward.


Question Authors' Forum -> Java method with state

by Peter Sander -

This is a prototype prototype (!) template for Java method questions, sort of like BUILT_IN_PROTOTYPE_java_method but with some differences:

  • the major difference is that the method can depend on an object state by referencing instance attributes
  • the method can refer to stuff in supporting classes
  • the class can be in a Java package (see another discussion in this forum)
As an example, let's say you want the students to write their getHello method for the following class:

public class Toto {
    private String hello = "Yo!";
    public String getHello() {
        return hello;
    }
}

Replace the method with the string PLACEHOLDER so that the class looks like:

public class Toto {
    private String hello = "Yo!";
    PLACEHOLDER
}

The template uses convention over configuration, so the above class must be in a support file called placeholder. The template replaces PLACEHOLDER by the student answer and runs the resulting class against your tests.

Here's the template:

import sys, os, shutil, subprocess
__student_answer__ = """{{ STUDENT_ANSWER | e('py') }}"""
# First replace the placeholder with the student answer
placeholder = '{{QUESTION.parameters.placeholder}}' or 'placeholder'
ncoding = '{{QUESTION.parameters.encoding}}' or 'utf-8'
with open(placeholder, mode='r', encoding=ncoding) as fd:
    __student_answer__ = fd.read().replace('PLACEHOLDER', __student_answer__)
    fd.close()
with open('{{ QUESTION.parameters.class}}.java', mode='w', encoding=ncoding) as fd:
    print(__student_answer__, file=fd)
    fd.close()
# Make package directory, put support files into it
package_dir = '{{ QUESTION.parameters.package }}'.replace('.', '/')
os.makedirs(package_dir)
[shutil.move(f, package_dir) for f in os.listdir() if f.endswith('.java')]
    
# Build test class in package directory too
tester = '''package {{ QUESTION.parameters.package }};
public class __Tester__ {
    public static void main(String[] args) {
        {{TEST.testcode}}
    }
}
'''
with open(package_dir + '/__Tester__.java', mode='w', encoding='utf-8') as fd:
    print(tester, file=fd)
    fd.close()
# Compile and run
if (subprocess.call(['javac', '-encoding', ncoding, package_dir + '/__Tester__.java']) != 0 or
   subprocess.call(['java', package_dir + '/__Tester__']) != 0):
    print('** Further testing aborted **', file=sys.stderr)