- Se recibe el fichero test.txt
- Se crea un directorio temporal donde sitúa el fichero recibido.
- Se copian en ese directorio los ficheros auxiliares del directorio base.
- Se ejecuta el comando
grep -c function test.txt - Se captura la salida de este comando (supongamos
3) - Esta salida se considera como la respuesta del alumno a una pregunta de respuesta libre
- Se busca el patrón que encaja con esta respuesta, para determinar si la respuesta es correcta.
- Se muestra al usuario su respuesta y la corrección (si el test lo permite)
intrucción == resultado -> etiqueta positiva : etiqueta negativa
o bien:intrucción <> resultado -> etiqueta positiva : etiqueta negativa
La prueba consiste en comprobar si el resultado de la ejecución de la instrucción encaja o no, dependiendo del signo de comparación, con un resultado esperado. Por ejemplo:grep -c function test.txt == <2|4> -> A : BEn este caso los patrones de respuesta que usa Siette deberían ser ''<!--A-->*'' y ''<!--B-->*''. El funcionamiento del sistema es el siguiente:
- Se recibe el fichero test.txt
- Se crea un directorio temporal donde situa el fichero
- Se copian en ese directorio los ficheros auxiliares del directorio base.
- Se ejecuta el comando
grep -c function test.txt - Se captura la salida de este comando (supongamos
3) - Se compara la salida con el patrón Siette
<2|4> - Si el patrón encaja se devuelve a Siette como respuesta del alumno
3, y si no encaja se devuelve3 - Se busca el patrón que encaje con esta respuesta, para determinar si la respuesta es correcta. En este caso los patrones de Siette pueden son
*y*, es decir dado que la respuesta ha sido3, el primer patrón. - Una vez que se ha determinado si la respuesta es correcta, o no, se muestra al usuario la corrección si el test lo permite. Nótese que el valor devuelto por el script (la etiqueta
A) se inserta dentro de un comentario HTML. Esto es así para evitar que la etiqueta sea visible al mostrar los resultados en Siette.
@patron [exec|file] : (true|false) , (true|false) , (true|false) , (true|false)
true o false que aparecen en esta sentencia de control corresponden a las posibles opciones de variabilidad de las respuestas, es decir, siguiendo el mismo orden:
- Equivalencia entre mayúsculas y minúsculas
- Equivalencia entre letras acentuadas y no acentuadas.
- Ignorar signos de puntuación
- Ignorar espacios en blanco
@SiettePattern: false, true, true, trueIndica que se usará el patrón de expresiones regulares Siette diferenciando entre mayusculas y minuúsculas, pero no teniendo en cuenta los acentos, ni los signos de puntuación, ni los espacios en blanco. Finalmente, hay una expresión especial
* que se cumple siempre y por tanto nunca se comprueba. En el siguiente ejemplo, se ha añadido la instrucción rm Punto.class para borrar el fichero .class si existe. Si no existe esta instrucción daría un mensaje de error. Da igual el resultado, la sentencia de prueba siempre tiene éxito.
@SiettePattern: true, true, true, true rm Punto.class == * -> A javac Punto.java <> *error* -> OK : ERROR_COMPILACION java Punto=== Opción file === La opción
file que puede aparecer o no en esta sentencia de control indica que el valor devuelto por la instrucción debe compararse con el contenido del fichero que aparece detrás del signo de comparación. En este caso, el segundo campo no contiene el resultado en si, sino el nombre del fichero en donde está el resultado previsto. La comparación se establece entre el resultado de la ejecución de la instrucción y el contenido del fichero. A su vez, dependiendo del tipo de patrón de comparación que se utilice (Correspondencia, PatronSiette, etc.) y de las opciones de comparación, el contenido del fichero contendrá el patrón de respuesta.
Por ejemplo, las sentencias:
@SiettePattern file: false, true, true, true cat test.txt == test1.txt -> A : Btienen como efecto comparar el contenido del fichero test.txt con el contenido del fichero test1.txt, o mejor dicho con la expresión regular contenida en este fichero, ya que se esta utilizando el patrón
@SiettePattern. No es una buena practica usar la comparación con ficheros de gran tamaño si se usan expresiones regulares, ya que es mucho mas ineficiente, y podría hacer que la prueba tardara demasiado. La opción file, en combinación con expresiones regulares solo esta indicada en ficheros pequeños.
=== Opción exec ===
La opción exec indica que el valor devuelto por la instrucción que aparece a la izquierda del signo de comparación debe compararse con el resultado de la ejecución de la instrucción que aparece detrás del signo de comparación. La comparación se establece entre el resultado de la ejecución de la primera instrucción y el resultado de ejecución de la segunda, asumiendo en su caso que la segunda pudiera ser una expresión regular, (dependiendo del tipo de patrón).
Por ejemplo, las sentencias:
@SiettePattern: false, true, true, true javac wc.java <> *error* -> OK : ERROR_COMPILACION @Correspondencia exec: true, true, true, true java wc test.txt == wc test.txt -> A : BEstablece la compilación y ejecución de un fichero wc.java cuya ejecución debe dar el mismo resultado que la ejecución de la instrucción wc del sistema operativo, (salvo diferencias en espacios en blanco, etc.) Si no hay ninguna sentencia de control para cambiar el patrón, por defecto se usa el patrón Java ==== Ejecución de una lista de sentencias de prueba ==== Cuando no hay una sino varias sentencias de prueba la ejecución de las sentencias viene controlada por los distintos modelos de tratamiento de errores que se establecen mediante sentencias de control. Supongamos que existen estas dos sentencias de prueba para comprobar si un programa en Python es capaz de detectar si una secuencia de números esta ordenada. La primera prueba debe dar como resultado
True y la segunda False
@Correspondence: true, true, true, true python3 test.py 1 2 3 4 == True -> : Incorrecto1 python3 test.py 1 5 3 4 == False -> Correcto : Incorrecto2Evidentemente en este caso interesa que se ejecuten ambas pruebas. Pueden ocurrir varias situaciones: (1) La primera prueba no da como resultado
False por lo que se devuelve a Siette la cadena ; (2) La primera
prueba se satisface, por lo que el script no termina, sino que continúa ejecutandose. Supongamos que la segunda preuba da un resultado True cuando debería dar False. La respuesta del sistema a Siette en este caso sería
; (3) Supongamos que la primera prueba se cumple y la segunda tambien. En este caso la respuesta que devuelve a Siette será . En realidad, este script podria sustituirse por este otro mas simple:
@Correspondence: true, true, true, true python3 test.py 1 2 3 4 == True -> : Incorrecto python3 test.py 1 5 3 4 == False -> Correcto : Incorrectosi no es necesario diferenciar entre uno u otro caso. ==== @Answer label ==== Cuando se trata de preguntas simples, Siette solo espera una respuesta tras la ejecución del script de verificación del fichero. Ahora bien, en el caso de preguntas compuestas, es necesario devolver una respuesta por cada una de las subpreguntas. Cuando se usan preguntas compuestas con fichero, el script correspondiente a la pregunta compuesta se obtiene mediante la concatenación de los scripts de cada una de las subpreguntas. La etiqeuta
@Answer se usa para de separar cada uno los trozos de script pertenecientes a cada una de las subpreguntas.
Este proceso es transparente al usuario, ya que al construir la pregunta externa de fichero, si se trata de una pregunta compuesta, Siette se preocupa de añadir para cada una de las subpreguntas la opción @Answer label correspondiente, de manera que cuando el script de procesamiento termina, ya sepa como comunicar la respuesta.
Podriamos decir que la sentencia @Answer es un sentencia de control del sistema, ya que el usuario normalmente no tiene que usarla.
==== @OnError stop ====
El modo descrito en los ejemplo anteriores es el comportamiento por defecto, que viene definido por la sentencia de control @OnError stop, cuyo funcionamiento general es el siguiente:
Supongamos un conjunto de sentencias de prueba
@OnError stop instr<sub>1</sub> == res<sub>1</sub> -> A<sub>1</sub> : B<sub>1</sub> instr<sub>2</sub> == res<sub>2</sub> -> A<sub>2</sub> : B<sub>2</sub> ... instr<sub>j-1</sub> == res<sub>j-1</sub> -> A<sub>j-1</sub> : B<sub>j-1</sub> instr<sub>j</sub> == res<sub>j</sub> -> A<sub>j</sub> : B<sub>j</sub> instr<sub>n</sub> == res<sub>n</sub> -> A<sub>n</sub> : B<sub>n</sub>El script ejecutara todas las instrucciones hasta llegar a una sentencia j que no se cumpla. En este momento debe decidir que etiqueta enviar a Siette. Si la etiqueta Bj no está vacía, utilizará esta etiqueta. En el caso en que Bj no exista, utilizará la última etiqueta correcta que haya alcanzado. Es decir, si existe Aj-1, enviará esta etiqueta, si no existe, enviará Aj-2 y así hasta llegar a A1. En caso de que tampoco exista enviará una etiqueta vacía. En el caso en el que se mezclen sentencias de prueba completas y sentencias de prueba simples, se entenderá que las sentencias de prueba simples siempre se cumplen y solo tienen asignadas etiquetas para el caso positivo. En el caso en el que todas las sentencias de prueba se satisfagan devolverá An si existe, y si no la etiqueta positiva mas reciente; y en el caso de que ninguna lo haga, devolverá B1, o la cadena vacía si ésta etiqueta no existiera. ==== @OnError continue ==== Mediante la sentencia de control
@OnError continue, se puede cambiar el modo en que se evalúan las sentencias de pruebas
Supongamos un conjunto de sentencias de prueba
@OnError continue instr(1) == res(1) -> A(1) : B(1) instr(2) == res(2) -> A(2) : B(2) ... instr(j-1) == res(j-1) -> A(j-1) : B(j-1) instr(j) == res(j) -> A(j) : B(j) instr(n) == res(n) -> A(n) : B(n)En el modo
@OnError continue se ejecutan todas las sentencias, y la respuesta a Siette viene determinada exclusivamente por la última sentencia, es decir que devolverá o bien An; o bien Bn
A primera vista parece que no tiene mucho sentido utilizar la opción @OnError continue, ya que el resultado solo depende de la última sentencia de prueba, sin embargo, en combinación con otras opciones cobra especial sentido, por ejemplo con la sentencia de control @Evall add all.
==== @OnError skip / @OnError recover ====
Esta sentencia de control se usa en combinación con la sentencia de control @OnError recover. Cuando en un script se establece el modo @OnError skip, si se produce un error en la ejecución de alguna de las sentencias de prueba el script no se interrumpe, sino que el flujo de control se reinicia y salta hasta encontrar la siguiente instrucción @OnError recover. Por ejemplo, supongamos el siguiente script:
@OnError skip instr1 == res1 -> A1 : B1 instr2 == res2 -> A2 : B2 instr3 == res3 -> A3 : B3 @OnError recover instr4 == res4 -> A4 : B4 instr5 == res5 -> A5 : B5 @OnError recover instr6 == res6 -> A6 : B6 instr7 == res7 -> A7 : B7 ...La ejecución comienza lanzando la instrucción instr1. Si la ejecución de instr1 da como resultado res1 el script de ejecución continúa con la instrucción instr2; pero si el resultado de la ejecución de instr1 no es el esperado, las instrucciones siguientes instr2 y instr3 no se ejecutarán. Al encontrar la sentencia de control
@OnError recover, el proceso se recupera, ejecutando ahora la instrucción instr4. Nuevamente si esta instrucción da un resultado correcto, el proceso seguirá con la instrucción siguiente, pero si la ejecución de
instr4 da error, la siguiente instrucción a ejecutar será instr6.
Nótese que la sentencia de control @OnError recover no tendría ningún efecto si previamente no es hubiera establecido la sentencia de control @OnError skip.
=== Preguntas compuestas ===
Un caso especial y muy frecuente del uso de estas sentencias de control es la ejecución de preguntas compuestas con fichero. Siette compone el script de ejecución añadiendo en la cabecera de cada subpregunta dos sentencias de control, una de tipo @Answer label y otra @OnError recover, por lo que solo sería necesario definir manualmente la sentencia de control @OnError skip. En este caso, no es necesario incluir manualmente @OnError recover ya que se añade automáticamente. El salto en la ejecución funcionará entre los (sub)scripts de las subpreguntas. Por ejemplo, supongamos que Siette ha compuesto el siguiente script:
@OnError skip instr(0,1) == res(0,1) -> A(0,1) : B(0,1) ... @Answer label(1) @OnError recover instr(1,1) == res(1,1) -> A(1,1) : B(1,1) instr(1,2) == res(2) -> A(2) : B(1,2) ... instr(1,j-1) == res(1,j-1) -> A(1,j-1) : B(1,j-1) instr(1,j) == res(1,j) -> A(1,j) : B(1,j) instr(1,n) == res(1,n) -> A(1,n) : B(1,n) @Answer label(2) @OnError recover instr(2,1) == res(2,1) -> A(2,1) : B(2,1) instr(2,2) == res(2) -> A(2) : B(2,2) ... instr(2,k-1) == res(2,k-1) -> A(2,k-1) : B(2,k-1) instr(2,k) == res(2,k) -> A(2,k) : B(2,k) instr(2,m) == res(2,m) -> A(2,m) : B(2,m) @Answer label(3) @OnError recover instr(3,1) == res(3,1) -> A(3,1) : B(3,1) ...Si suponemos que las sentencias de pruebas fallan en la prueba instr1,j == res1,j, la ejecución saltara a la prueba instr2,1 == res2,1. Si suponemos que a partir de aquí todas las pruebas funcionan hasta la prueba instr2,k == res2,k, el flujo de control volverá a saltar hasta la siguiente instrucción
OnError recover situada justo detrás de @Answer label3, es decir a la prueba instr3,1 == res3,1, y así sucesivamente.
La instrucción @Answer label devuelve en cada caso la etiqueta correspondiente al subscript que comienza a partir de esta sentencia, es decir, en el supuesto anterior, la pregunta externa de fichero devolverá a Siette el valor de la etiqueta B1,j (y si no existe esta etiqueta A1,j-1, y si no el anterior, y así sucesivamente), en el parámetro label1; y B1,k (o en su defecto la etiqueta que corresponda), como valor del parámetro label2.
==== @Eval add all ====
Esta sentencia de control indica que cuando se devuelve un valor a Siette se deben sumar todas las etiquetas que se hayan producido, y no solamente utilizar la última. Evidentemente esto solo puede hacerse si las etiquetas tienen un valor numérico. Si no lo tienen o no se han escrito etiquetas en la sentencia de prueba, se tomara como valor 0.
Por ejemplo, supongamos que se quiere comprobar si un programa en Python es capaz de reconocer los numeros impares. El script podría ser el siguiente:
@OnError continue @Eval add all python3 test.py 1 == True -> 1 python3 test.py 2 == False -> 1 python3 test.py 3 == True -> 1 python3 test.py 4 == False -> 1 python3 test.py 5 == True -> 1 python3 test.py 6 == False -> 1Cada una de estas instrucciones se ejecuta dando un resultado satisfactorio, o no, dependiendo de que se cumpla o no la igualdad. Puesto que el modo de evaluacion es
@OnError continue se ejecutarán todas las sentencias, pero solo algunas tendrán exito. El valor devuelto por Siette en este caso será si y solo si, las seis sentencias de prueba tienen exito. Si por ejemplo el programa devuelve True en todos los casos, la suma será de . Puesto que esto no parece muy buena estrategia, el script podria mejorarse asi:
@OnError continue @Eval add all python3 test.py 1 == True -> 1 : -1 python3 test.py 2 == False -> 1 : -1 python3 test.py 3 == True -> 1 : -1 python3 test.py 4 == False -> 1 : -1 python3 test.py 5 == True -> 1 : -1 python3 test.py 6 == False -> 1 : -1Con este script en el caso de que siempre devuelva
True, al sumar los resultados se obtiene como respuesta
Téngase en cuenta que, en general, para que esta opción funcione, es necesario activar también la opción @OnError continue para que la ejecución no se detenga en el primer fallo.
Por otra parte, para que la evaluación tenga efecto habra que considerar todas las posibles combinaciones de valores. Por ejemplo, en el caso anterior las posibles opciones de respuesta son trece, que varían entre y , aunque quizas solo sea necesario contemplar las opciones a . No olvide seleccionar en este caso el modo de evaluación manual y asignar valores menores que uno a las respuestas parcialmente correctas, es decir las opciones de a .
==== @Eval add correct ====
Esta opción es similar a la anterior, pero solamente suma los valores corespondientes a las etiquetas de las sentencias de prueba que han dado un resultado positivas.
==== @Eval max correct ====
Toma como valor devuelto el de la última etiqueta no vacía de una sentencia de prueba que haya tenido éxito. Si no hay ejecucion correcta, devuelve la ultima etiqueta no vacia. El término max hace referencia a la última que se ejecuto, no al valor numérico.
==== @Eval max all ====
En este caso, se toma como valor devuelto el de la última etiqueta no vacía de una sentencia de prueba haya tenido éxito o no. El término max hace referencia a la última que se ejecuto, no al valor numérico.
Este es el valor de la evaluación por defecto.
==== @Scale====
La sentencia de control @Scale se usa para informar al sistema de evaluación que se va a utilizar una cierta escala para la evaluación en el caso de que la etiqueta que el script devolverá a Siette tenga un valor numérico. . La sintaxis de estas sentencias es la siguiente:
@timeout NUMERO1 NUMERO2
en donde NUMERO1 es un número entero positivo que representa el valor numérico máximo posible que resultaría como etiqueta final de la evaluación, y NUMERO2 el valor máximo de la escala que debería tener la etiqueta devuelta. Por consiguiente, la etiqueta final, en el caso de usar una etiqueta numérica, no corresponde al valor que se muestra en el script, sino a su valor transformado en la escala [0,NUMERO2]. Este es el caso por ejemplo del script:@OnError continue @Eval add correct @Scale 6 4 python3 test.py 1 == True -> 1 python3 test.py 2 == False -> 1 python3 test.py 3 == True -> 1 python3 test.py 4 == False -> 1 python3 test.py 5 == True -> 1 python3 test.py 6 == False -> 1La instrucción
@OnError continue hace que la ejecución no se detenga tras la primera prueba fallida, sino que continue con la siguiente. La instrucción @Eval add all informa a Siette de que debe sumar las etiquetas que hayan obtenido un resultado correcto, por lo que de las 6 lineas que hay en el script, si algunas resultan correctas y otras no, se pueden obtener como resultado de la evaluación las etiquetas “”, “1”, “2”, “3”, “4”, “5” y “6”.
La instrucción de escala @Scale 6 4 indica a Siette que el valor máximo que se puede obtener es 6, y que debe ser modificado en una escala que tenga como valor máximo 4. Esta operación dará como resultado siempre un número entero entre 0 y 4, truncando la parte decimal, por tanto los posibles valores por Siette son: “”, “0”, “1”, “2”, “2”, “3” y “4” para cada uno de los casos obtenidos anteriormente.
La gran ventaja de esta normalización es que el conjunto de etiquetas no depende del número de pruebas satisfactorias, ni de las posibles combinaciones de valores, que en algunos casos en donde hay muchas pruebas o las pruebas tienen distinto valor pueden dar lugar a un conjunto de etiquetas demasiado grande.
==== @timeout ====
La sentencia de control @tmeout sirve para imponer un limite de tiempo en la ejecución de cada una de las instrucciones del sistema operativo. La sintáxis de estas sentencias es la siguiente:
@timeout NUMERO
en donde NUMERO es un número entero positivo que representa el límite de tiempo en segundos. Auqnue al ejecutar una pregunta con fichero puede establecerse un límite de tiempo global para el script completo (en la pestaña Avanzado), el mecanismo por el cual se limita el tiempo de ejecución del script no es tan preciso, ni tan fiable como el mecanismo puede establecerse mediante la sentencia de control@timeout, ya que esta última impone un control desde el propio sistema operativo, mientras que la anterior lo hace sobre una hebra del programa Java que se encarga de ejecutar el script.
A bajo nivel, la instrucción @timeout hace que la ejecución de las sentencias de prueba añadan a la instrucción del sistema operativo un prefijo con una instrucción de tipo timeout. El valor exacto dependerá de la configuración de Siette. Un valor común de este prefijo es: timeout -k $1 $1 en donde $1 representa el tiempo límite. Por ejemplo, el script:
@OnError stop javac Punto.java <> *error* -> : ERROR_COMPILACION @timeout 10 java Punto 1 == 1,2 -> : ERROR_EJECUCION java Punto 2 == 3,4 -> : ERROR_EJECUCION java Punto 3 == 5,6 -> OK : ERROR_EJECUCIONal invocar la ejecución sobre el sistema operativo de las instrucciones de las sentencia de prueba ejecutará realmente las siguientes instrucciones, siempre que no se produzca un error que detenga el script. Estas instrucciones nunca se muestran realmente al usuario, ya que este proceso queda oculto.
javac Punto.java timeout -k 10 10 java Punto 1 timeout -k 10 10 java Punto 2 timeout -k 10 10 java Punto 3En caso de que alguna de las sentencias no concluya su ejecución en 10 segundos, la instrucción
timeout forzará una señal kill para detener el proceso por lo que presumiblemente el resultado de la ejecución será vacío y desde el punto de vista de la sentencia de control, se producirá un error.
En casos en los que se prevea la necesidad de usar límites de tiempo, por ejemplo, si se prevee que ejecución de una instrucción pudiera ocasionar un bucle sin final o un consumo excesivo de recursos, etc. es muy recomendable usar esta opción.
==== @ssh ====
Esta sentencia de control sirve para indicar que las sentencias de prueba no se ejecutan sobre el servidor principal de Siette sino sobre un servidor remoto al cual tiene acceso el servidor de Siette. La sintáxis de estas sentencias es la siguiente:
@ssh user@host
en donde user@host es el usuario y la direccíón IP (simbolica o numérica), donde realmente se ejecutarán las sentencias de prueba. Para que el mecanismo funcione, la máquina donde reside el servidor de Siette, y el usuario que ejecuta el proceso de la aplicación web Siette (normalmente el usuariotomcat) debe tener acceso directo al host remoto, sin que sea necesaria la identificación mediante contraseña. ((Para poder utilizar este servicio el administrador de la máquina en donde reside Siette debe haber configurado manualmente este acceso mediante un par de clavés publica y privada. Las manera de configurarlo varía poco de un sistema operativo a otro. Por ejemplo, vea las instrucciones para Ubuntu.