Object-oriented code-runner in Octave?

Object-oriented code-runner in Octave?

by Angus Wallace -
Number of replies: 12

Is it possible to allow a student to define a class (using classdef) in code-runner, then create the object and run/test it? I can't seem to create new classes in the Octave command-window -- it needs to be in a file saved with the name of the class. Is there a way around this?

Thanks, Angus

In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Richard Lobb -

Hi Angus

Anything you can do by hand you can do with a script. So the top level script (the template) takes the student answer (as a string) and writes it to a file. What you do next, depends on what you're scripting in. If it's Octave, I seem to remember calling rehash so the current directory gets re-read, then continuing with the actual test code. If you're scripting in Python (my choice, nowadays) you'd write the test code to an Octave test file and run that in a subprocess.

Is that enough to get you hacking, or do you need more detail?  :-)

Richard

In reply to Richard Lobb

Re: Object-oriented code-runner in Octave?

by Angus Wallace -

Thanks Richard,

For now, I've stayed working in Matlab/Octave

If I understand you correctly, I need to modify the template so that the student's answer is written to file, then call rehash so that octave sees it?

So I would change this bit?

<template>{{ TEST.testcode }};
format free;
{{ STUDENT_ANSWER }}
{{ TEST.extra }};
</template>

I've tried changing it to what I've included below, but it doesn't seem to work either. Sorry, I'm not really clear how the back-end of the template stuff works. How should I write the student answer string to a file?

Many thanks, Angus

<template>{{ TEST.testcode }};
format free;
fid = fopen('Car.m');
fprintf(fid, {{ STUDENT_ANSWER }})
fclose(fid)
rehash
{{ TEST.extra }};


In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Richard Lobb -

Oh wow! You're programming by editing XML and importing?

Try the following (off the top of my head, it's bedtime in NZ):

  1. Create a new Octave question.
  2. In the question authoring form, click "Customise" and "Template debugging"
  3. Scroll down to the newly appeared Template section of the author form.
  4. Turn off the "Is combinator" checkbox
  5. Replace the template code with something like the following. I'm assuming the student answer is a class you wish to write to a file.
    student_answer = sprintf("{{ STUDENT_ANSWER | e('matlab') }}");
    fid = fopen("Car.m", "w");
    fputs(fid, student_answer);
    fclose(fid);
    rehash();
    {{ TEST.testcode }}
    
  6. Save the question
  7. Run it
  8. Study the template debugging output to see what program is being run, and hence debug it.

Hopefully, you'll find that approach easier than editing XML every time :)

Richard

In reply to Richard Lobb

Re: Object-oriented code-runner in Octave?

by Angus Wallace -

You're amazing, Richard -- thanks so much for your endless help :-)

I've tried a heap of things in the question authoring form (yes, it's a lot easier than to edit and import XML each time!)

I think it's closer, but I'm still getting an error:

***Runtime error***
error: invalid use of script /optCar.m in index expression
error: called from:
error:   /optprog.octave at line 6, column 12
Line 6 is where I run the class constructor (I think), so it looks like Octave is having trouble with the file. Do I need to change the name of the class to something else? Any ideas?

Thanks again, Angus

In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Richard Lobb -

I just tried my template and hit a problem, too. It seems that Octave caches classdefs, so that once you've loaded a classdef it stays loaded, even across multiple launches of Octave. [Yuck!]

The solution is to do a clear all at the start of the script. The following template is now working for me:

clear all;
student_answer = sprintf("{{ STUDENT_ANSWER | e('matlab') }}");
fid = fopen("Car.m", "w");
fputs(fid, student_answer);
fclose(fid);
rehash();
{{ TEST.testcode }}

The test code is

mycar = Car('Ford', 23000);
fprintf('A %s with %d km on the clock\n', mycar.brand, mycar.mileage);

and the expected answer is

classdef Car
    properties
        brand, mileage
    end
    methods
        function self = Car(brand, mileage)
            self.brand = brand;
            self.mileage = mileage;
        end
    end
end

Here's a screenshot
Screenshot of working Classdef question

However, one thing about your output that worries me is the pathnames like /optcar.m and /optprog.octave. These imply that you're writing files to the root of the file system. If so, this is seriously wrong. It shouldn't even be possible for the web server to write to the root directory - jobe would need to be seriously misconfigured to allow that. The only directory that a jobe task should be writing to is the current working directory, which is a temporary directory set up just for one run and which can't be seen by any other jobs running at the same time. [The task can write to /tmp also, but I don't recommend that.]

Can you clarify what is going on here, please?

Richard

In reply to Richard Lobb

Re: Object-oriented code-runner in Octave?

by Angus Wallace -
Hi Richard,

Thanks for that. I think you're right that there might be a problem with where the files were being written.
I changed the template to:
clear all;
student_answer = sprintf("{{ STUDENT_ANSWER | e('matlab') }}");
cd /tmp
fid = fopen("Car.m", "w");
fputs(fid, student_answer);
fclose(fid);
rehash();
{{ TEST.testcode }}
and now receive this error:
error: invalid use of script /tmp/Car.m in index expression
error: called from:
error:   /optprog.octave at line 8, column 10
which suggests that before it was writing to /  (I presume this is a pretty major security risk, and implies that either the service is running as root or that there are write permissions to /)

I don't have admin rights on the server, so am following up with those that do...

Thanks very much, Angus

In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Angus Wallace -

Hi Richard,

All the problems were down to having an old version of Octave installed. IT upgraded it to version 4.x and now it is working.

Well... almost. My simple Car class is working well, but I have another class that has strings in it (the Car class does not) and it now appears that there is some problem because coderunner appears to be replacing all string quotes with double quotes which is breaking it. (I am unable to create an object from within coderunner). Do you know if there is some kind of interaction between:

student_answer = sprintf("{{ STUDENT_ANSWER | e('matlab') }}");
and the classdef file which would break things when there are string fields/methods in the class?

Thanks again!

-Angus

In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Richard Lobb -

Sorry, I made an error in the template I gave you. It should be

student_answer = sprintf('{{ STUDENT_ANSWER | e('matlab') }}');

with single quotes around the sprintf argument.

Matlab uses single quotes for char vectors and the Matlab escaper assumes that the result is to be a char vector, suitable for use in sprintf/fprintf etc (because that's the only place that traditional Matlab would recognise '\n' as a single newline character). The way to inject a single quote into a char vector literal is to double it, which is what the escaper does.

Octave allows double quotes for char vector delimiters but it doesn't then process doubled single quotes within the string. And with Matlab now introducing a string data type that is delimited by double quotes there's now a nasty Octave/Matlab incompatibility.

BTW: when you say the old Octave version was the problem before, are you saying that that was explanation for the '/' at the start of the file paths??

Richard


In reply to Richard Lobb

Re: Object-oriented code-runner in Octave?

by Angus Wallace -

Brilliant -- that's done it. Thanks Richard :-)

Regarding the old version of Octave, I don't think that was the explanation for the strange file paths, but I don't think those paths were the source of the problem. I queried this with IT support (who were extremely helpful and responsive, bless them!) and my understanding is that each student response is run in a chroot, which is why it looks like it's running in / even though it's not.

Versions of Octave <4.0 don't support the classdef method of class definitions. Now that we've upgraded to >4.0, it is working beautifully -- including the strings in classes with your latest suggestion.

Thanks again for all your help,

-Angus

In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Angus Wallace -
Hi Richard,

One other (hopefully) quick question. Is it possible for me to have students submit two classes that have a separator between them, and change the template to something like:

clear all
format free;
student_answer = strsplit(sprintf('{{ STUDENT_ANSWER | e('matlab') }}',**^*--AWKseparator-DONT-TOUCH-AWKseparator--**^*);
fid = fopen("ClockDisplay.m", "w");
fputs(fid, student_answer{1});
fclose(fid);
fid = fopen("NumberDisplay.m", "w");
fputs(fid, student_answer{2});
fclose(fid);
rehash();
{{ TEST.testcode }};
{{ TEST.extra }};

Thanks, Angus
In reply to Angus Wallace

Re: Object-oriented code-runner in Octave?

by Richard Lobb -

Hi Angus

That looks OK to me, apart from the missing quotes around the strsplit separator. Give it a go and let me know if it doesn't work.

BTW: you know you can tick the "Template debugging" checkbox and get to see exactly what program gets run, do you? In tricky situations you can then copy the program out into your IDE and debug it there rather than working within CodeRunner all the time.

Richard

In reply to Richard Lobb

Re: Object-oriented code-runner in Octave?

by Angus Wallace -

Great, thanks, yes I've got it to work.

A minor gotcha, I needed to start the separator with a comment -- not quite sure why, as I thought it was being interpreted before Octave saw it. Anyway, it seemed to be needed.

It's pretty neat, actually. The students see two classes that they need to complete/modify for a desired task. Octave sees them as though they are two saved classdef files. Seems to work well!

Cheers, Angus