示例#1
0
  def source_file(self, assignment, f):
    """
    Function: source_file
    ---------------------
    Sources a file into the database. Since the "source" command is for the
    MySQL command-line interface, we have to parse the source file and run
    each command one at a time.

    assignment: The assignment name, which is prepended to all the files.
    f: The source file to source.
    """
    try:
      fname = ASSIGNMENT_DIR + assignment + "/" + f
      f = codecs.open(fname, "r", "utf-8")
    except IOError:
      err("Could not find or open sourced file %s!" % fname, True)

    sql_list = split(preprocess_sql(f))
    for sql in sql_list:
      # Skip this line if there is nothing in it.
      if len(sql.strip()) == 0:
        continue
      # Otherwise execute each line. Output must be consumed for the query
      # to actually be executed.
      sql = sql.rstrip()
      # if VERBOSE:
      #   print("-" * 78)
      #   print("source_file(%s):  Running SQL command:\n%s" % (fname, sql))
      for _ in self.cursor.execute(sql, multi=True): self.clear_cursor()
      self.commit()
    f.close()
示例#2
0
    def source_file(self, assignment, f):
        """
    Function: source_file
    ---------------------
    Sources a file into the database. Since the "source" command is for the
    MySQL command-line interface, we have to parse the source file and run
    each command one at a time.

    assignment: The assignment name, which is prepended to all the files.
    f: The source file to source.
    """
        try:
            fname = ASSIGNMENT_DIR + assignment + "/" + f
            f = codecs.open(fname, "r", "utf-8")
        except IOError:
            err("Could not find or open sourced file %s!" % fname, True)

        sql_list = split(preprocess_sql(f))
        for sql in sql_list:
            # Skip this line if there is nothing in it.
            if len(sql.strip()) == 0:
                continue
            # Otherwise execute each line. Output must be consumed for the query
            # to actually be executed.
            sql = sql.rstrip()
            # if VERBOSE:
            #   print("-" * 78)
            #   print("source_file(%s):  Running SQL command:\n%s" % (fname, sql))
            for _ in self.cursor.execute(sql, multi=True):
                self.clear_cursor()
            self.commit()
        f.close()
示例#3
0
  def __init__(self, user, database):
    # The database connection parameters are specified in the CONFIG.py file.
    # Contains the reference to the database object.
    self.db = None

    # The database cursor.
    self.cursor = None

    # The current connection timeout limit.
    self.timeout = CONNECTION_TIMEOUT

    # The savepoints.
    self.savepoints = []

    # The user to connect with. If no user is specified, picks the first user
    # in the dictionary.
    self.user = user if user is not None else LOGIN.keys()[0]

    # The name of the database to connect to. If none is specified, use
    # <user>_db as the default database.
    self.database = database if database is not None else "%s_db" % self.user

    # Separate database connection used to terminate queries. If the terminator
    # cannot start, the grading cannot occur.
    try:
      self.terminator = Terminator(self.user, self.database)
    except mysql.connector.errors.Error:
      err("Could not start up terminator connection! Any unruly queries " +
          "must be manually killed!")
示例#4
0
    def import_file(self, assignment, f):
        """
    Function: import_files
    ----------------------
    Imports raw data files into the database. This uses the "mysqlimport"
    command on the terminal. We will have to invoke the command via Python.

    assignment: The assignment name, which is prepended to all the files.
    f: The file to import.
    """
        log("\nImporting file " + f + "...\n")
        filename = ASSIGNMENT_DIR + assignment + "/" + f

        # Make sure the file exists.
        if not os.path.exists(filename):
            err("File to import %s does not exist!" % filename, True)
        try:
            subprocess.call("mysqlimport -h " + HOST + " -P " + PORT + " -u " +
                            self.user + " -p" + LOGIN[self.user] +
                            " --delete --local " + self.database + " " +
                            filename,
                            shell=True)
        except OSError:
            err(
                "Could not import file %s! The 'mysqlimport' utility does not exist!"
                % filename, True)
示例#5
0
    def __init__(self, user, database):
        # The database connection parameters are specified in the CONFIG.py file.
        # Contains the reference to the database object.
        self.db = None

        # The database cursor.
        self.cursor = None

        # The current connection timeout limit.
        self.timeout = CONNECTION_TIMEOUT

        # The savepoints.
        self.savepoints = []

        # The user to connect with. If no user is specified, picks the first user
        # in the dictionary.
        self.user = user if user is not None else LOGIN.keys()[0]

        # The name of the database to connect to. If none is specified, use
        # <user>_db as the default database.
        self.database = database if database is not None else "%s_db" % self.user

        # Separate database connection used to terminate queries. If the terminator
        # cannot start, the grading cannot occur.
        try:
            self.terminator = Terminator(self.user, self.database)
        except mysql.connector.errors.Error:
            err("Could not start up terminator connection! Any unruly queries "
                + "must be manually killed!")
示例#6
0
  def grade_loop(self):
    """
    Function: grade_loop
    --------------------
    Run the grading loop. Goes through each student and grades them.
    """
    log("\n\n========================START GRADING========================\n")
    # Get the state of the database before grading.
    state = self.db.get_state()

    # Keep a list of students that we could not grade.
    failed_grading = []
    possibly_failed = False
    possibly_failed_grading = []

    # Grade each student.
    if len(self.students) == 0:
      err("No students to grade!")

    i_student = 0
    n_students = len(self.students)
    for student in self.students:
      i_student += 1
      try:
        self.grade_student(student, i_student, n_students)
        # If we've managed to grade this student, remove them from the students
        # that we could not grade.
        if student in failed_grading:
          failed_grading.remove(student)
      except Exception:
        # Don't try to regrade again if failed once already.
        if student not in failed_grading:
          failed_grading.append(student)
          self.students.append(student)
          print "\nFailed grading " + student + ", trying one more time.\n"
          traceback.print_exc()
        else:
          print "\nFailed grading " + student + " again. Giving up.\n"

      # Get the state of the database after the student is graded and reset it
      # to what it was before.
      try:
        new_state = self.db.get_state()
        self.db.reset_state(state, new_state)
      except:
        possibly_failed = True
        err("Could not get the database state. Future gradings are possibly " +
          "affected.")
      if possibly_failed and student not in possibly_failed_grading:
        possibly_failed_grading.append(student)

    log("\n\n=========================END GRADING=========================\n")

    if len(failed_grading) > 0:
      print "\nFAILED GRADING:",
      print ", ".join(failed_grading)
    if len(possibly_failed_grading) > 0:
      print "\nPOSSIBLY FAILED (could not get the database state):",
      print ", ".join(possibly_failed_grading)
示例#7
0
  def grade_student(self, student, i_student, n_students):
    """
    Function: grade_student
    -----------------------
    Grades a particular student. Outputs the results to a file.

    student: The student's name.
    """
    log("\n\n%s (%d/%d):" % (student, i_student, n_students))

    # Check to see that this student exists. If not, skip this student.
    path = ASSIGNMENT_DIR + self.assignment + "/" + STUDENT_DIR + student + \
           "-" + self.assignment + "/"
    if not os.path.exists(path):
      err("Student " + student + " does not exist or did not submit!")
      return

    # Graded output for this particular student. Add it to the overall output.
    output = {"name": student, "files": {}, "got_points": 0}
    self.o.fields["students"].append(output)

    # Parse student's response.
    response = {}
    for filename in self.files:
      # Add this file to the graded output.
      graded_file = {
        "filename": filename,
        "problems": [],
        "errors": [],
        "got_points": 0
      }
      output["files"][filename] = graded_file
      fname = path + filename

      try:
        f = open(fname, "r")
        # Run their files through the stylechecker to make sure it is valid. Add
        # the errors to the list of style errors for this file and overall for
        # this student.
        graded_file["errors"] += StyleChecker.check(f)

        # Reset back to the beginning of the file.
        f.seek(0)
        response[filename] = iotools.parse_file(f)
        f.close()

      # If the file does not exist, then they get 0 points.
      except IOError:
        add(graded_file["errors"], FileNotFoundError(fname))

    # Grade this student, make style deductions, and output the results.
    output["got_points"] = self.grader.grade(response, output)
    formatter.format_student(student, output, self.specs, self.hide_solutions)
示例#8
0
    def savepoint(self, savepoint):
        """
    Function: savepoint
    -------------------
    Creates a savepoint with the specified name.

    savepoint: The name of the savepoint.
    """
        try:
            self.execute_sql("SAVEPOINT %s" % savepoint)
        except mysql.connector.errors.Error:
            err("Could not create savepoint %s!" % savepoint)

        # If this savepoint name already exists, add and remove it.
        if savepoint in self.savepoints:
            self.savepoints.remove(savepoint)
        self.savepoints.append(savepoint)
示例#9
0
  def savepoint(self, savepoint):
    """
    Function: savepoint
    -------------------
    Creates a savepoint with the specified name.

    savepoint: The name of the savepoint.
    """
    try:
      self.execute_sql("SAVEPOINT %s" % savepoint)
    except mysql.connector.errors.Error:
      err("Could not create savepoint %s!" % savepoint)

    # If this savepoint name already exists, add and remove it.
    if savepoint in self.savepoints:
      self.savepoints.remove(savepoint)
    self.savepoints.append(savepoint)
示例#10
0
    def kill_query(self):
        """
    Function: kill_query
    --------------------
    Kills the running query by terminating the connection.
    """
        if not self.db or not self.db.is_connected():
            return

        thread_id = self.db.connection_id
        try:
            self.terminator.terminate(thread_id)
        except mysql.connector.errors.Error:
            err("Unable to kill %d (was probably already killed)." % thread_id)
        # If the terminator doesn't even exist, then this is a problem.
        except AttributeError:
            err("Terminator doesn't exist to kill queries!", True)
        self.savepoints = []
示例#11
0
  def kill_query(self):
    """
    Function: kill_query
    --------------------
    Kills the running query by terminating the connection.
    """
    if not self.db or not self.db.is_connected():
      return

    thread_id = self.db.connection_id
    try:
      self.terminator.terminate(thread_id)
    except mysql.connector.errors.Error:
      err("Unable to kill %d (was probably already killed)." % thread_id)
    # If the terminator doesn't even exist, then this is a problem.
    except AttributeError:
      err("Terminator doesn't exist to kill queries!", True)
    self.savepoints = []
示例#12
0
  def setup(self):
    """
    Function: setup
    ---------------
    Sets up the grading environment and tools. This includes establishing the
    database connection, reading the specs file, sourcing all dependencies,
    and running setup queries.
    """
    # The graded output.
    self.o = GradedOutput(self.specs)
    # Start up the connection with the database.
    self.db = dbtools.DBTools(self.user, self.db)
    try:
      self.db.get_db_connection(MAX_TIMEOUT)
    except DatabaseError:
      err("Could not get a database connection! Please check your internet " +
          "connection and database connection information!",
          True)

    # Purge the database if necessary.
    if AutomationTool.purge: self.db.purge_db()

    # Source and import files needed prior to grading and run setup queries.
    if self.specs.get("setup"):
      for item in self.specs.get("setup"):
        if item["type"] == "dependency" and AutomationTool.dependency:
          if VERBOSE:
            print("Sourcing dependency file %s" % item["file"])

          self.db.source_file(self.assignment, item["file"])
        elif item["type"] == "import" and AutomationTool.dependency:
          if VERBOSE:
            print("Importing file %s" % item["file"])

          self.db.import_file(self.assignment, item["file"])
        elif item["type"] == "queries": 
          if VERBOSE:
            print("Running initial queries:\n * %s" % '\n * '.join(item["queries"]))

          for q in item["queries"]: self.db.execute_sql(q)

    # Initialize the grading tool.
    self.db.get_db_connection(CONNECTION_TIMEOUT)
    self.grader = Grader(self.assignment, self.specs, self.db)
示例#13
0
  def import_file(self, assignment, f):
    """
    Function: import_files
    ----------------------
    Imports raw data files into the database. This uses the "mysqlimport"
    command on the terminal. We will have to invoke the command via Python.

    assignment: The assignment name, which is prepended to all the files.
    f: The file to import.
    """
    log("\nImporting file " + f + "...\n")
    filename = ASSIGNMENT_DIR + assignment + "/" + f

    # Make sure the file exists.
    if not os.path.exists(filename):
      err("File to import %s does not exist!" % filename, True)
    try:
      subprocess.call("mysqlimport -h " + HOST + " -P " + PORT + " -u " +
                      self.user + " -p" + LOGIN[self.user] +
                      " --delete --local " + self.database + " " + filename, shell=True)
    except OSError:
      err("Could not import file %s! The 'mysqlimport' utility does not exist!" % filename,
          True)
示例#14
0
  def get_args(self):
    """
    Function: get_args
    ------------------
    Gets the command-line arguments and prints a usage message if needed.
    Figures out the files and students to grade.
    """
    # Parse command-line arguments.
    parser = argparse.ArgumentParser()

    # The grading-specific arguments.
    parser.add_argument("--assignment", required=True,
                        help="Name of the assignment (cs121hw#)")
    parser.add_argument("--files", nargs="+", help="List of files to check")
    parser.add_argument("--students", nargs="+", help="Students to check")
    parser.add_argument("--startwith", nargs="+", help="Which student to start with")
    parser.add_argument("--exclude", nargs="+", help="Students to skip")
    parser.add_argument("--after", help="Of the form YYYY-MM-DD, will only "
                                        "grade students who've submitted after "
                                        "that date. Cannot be used with the "
                                        "--students flag")
    # Database-specific arguments.
    parser.add_argument("--user", help="Username for the database, defaults to "
                                       "a random one in the CONFIG")
    parser.add_argument("--db", help="Database to test on, defaults to "
                                     "<username>_db")
    parser.add_argument("--deps", action="store_const", const=True,
                        help="Whether or not to run the dependencies. Should "
                             "only be run once with this flag unless a purge "
                             "occurs, since there is no point running "
                             "dependencies more than once if they are already "
                             "in the database")
    parser.add_argument("--purge", action="store_const", const=True,
                        help="Whether or not to purge the database before"
                             " grading")
    parser.add_argument("--hide", action="store_const", const=True,
                        help="Whether or not to hide solutions from the output,"
                             " used to generate output for students")
    parser.add_argument("--raw", action="store_const", const=True,
                        help="Whether or not to output results as a raw JSON "
                             "file")
    args = parser.parse_args()
    (self.assignment, self.files, self.students, self.start_with, exclude, after,
     self.user, self.db, AutomationTool.purge, AutomationTool.dependency,
     AutomationTool.hide_solutions, AutomationTool.raw) = (
        args.assignment, args.files, args.students, args.startwith, args.exclude,
        args.after, args.user, args.db, args.purge, args.deps, args.hide, args.raw)

    # If the assignment argument isn't specified, print usage statement.
    if self.assignment is None:
      parser.print_help()
      sys.exit(1)

    # Get the specs, files, and students for this assignment.
    self.specs = iotools.parse_specs(self.assignment)

    # If nothing specified for the files, grade all the files. Make sure the
    # specified files are all valid.
    if self.files is None or self.files[0] == "*":
      self.files = self.specs["files"]
    for f in self.files:
      if f not in self.specs["files"]:
        err("File %s is not in the specs!" % f)
        self.files.remove(f)
    if len(self.files) == 0:
      err("No valid files specified for grading!", True)

    # If nothing specified for the students, grade all the students.
    if self.students is None or self.students[0] == "*":
      self.students = iotools.get_students(self.assignment, after)

    if self.start_with:
      self.start_with = self.start_with[0]
      try:
        start_index = self.students.index(self.start_with)
        self.students = self.students[start_index:]
      except ValueError:
        err("Couldn't find %s in students." % self.start_with, True)

    # Take out students to skip.
    if exclude:
      self.students = [s for s in self.students if s not in exclude]
示例#15
0
    def reset_state(self, old, new):
        """
    Function: reset_state
    ---------------------
    Resets the state of the database from 'new' back to 'old'. This involves
    removing all functions, views, functions, procedures, and triggers that
    have been newly created.

    old: The old state of the database to be reverted back to.
    new: The new (current) state of the database.
    """
        new.subtract(old)

        try:
            # Drop all functions procedures, and triggers first.
            # if VERBOSE:
            #   print("-" * 78)
            #   print("Resetting state.")

            for trig in new.triggers:
                sql = "DROP TRIGGER IF EXISTS %s" % trig
                # if VERBOSE:
                #   print(sql)

                self.execute_sql(sql)

            for proc in new.procedures:
                sql = "DROP PROCEDURE IF EXISTS %s" % proc
                # if VERBOSE:
                #   print(sql)

                self.execute_sql(sql)

            for func in new.functions:
                sql = "DROP FUNCTION IF EXISTS %s" % func

                # if VERBOSE:
                #   print(sql)

                self.execute_sql(sql)

            # Drop views.
            for view in new.views:
                sql = "DROP VIEW IF EXISTS %s" % view

                # if VERBOSE:
                #   print(sql)

                self.execute_sql(sql)

            # Drop tables. First must drop foreign keys on the tables in order to be
            # able to drop the tables without any errors.
            for (table, fk) in new.foreign_keys:
                self.execute_sql("ALTER TABLE %s DROP FOREIGN KEY %s" %
                                 (table, fk))
            for table in new.tables:
                self.execute_sql("DROP TABLE IF EXISTS %s" % table)
        except (mysql.connector.errors.Error, DatabaseError, TimeoutError):
            err("Could not reset database state. Possible errors in future grading."
                )

        # Remove all savepoints.
        self.savepoints = []
示例#16
0
    Function: teardown
    ------------------
    Outputs the results, runs the teardown queries and closes the database
    connection.
    """
    # Output the results to file, but only if there are students to output.
    json_output = json.loads(self.o.jsonify())
    if json_output["students"]:
      f = iotools.output(json_output, self.specs, self.raw)
      log("\n\n==== RESULTS: " + f.name)

    # Run teardown queries.
    if self.specs.get("teardown"):
      for query in self.specs["teardown"]:
        self.db.execute_sql(query)

    # Close connection with the database
    self.db.close_db_connection()


if __name__ == "__main__":
  a = AutomationTool()
  try:
    a.get_args()
    a.setup()
    a.grade_loop()
    a.teardown()
  except Exception:
    err("\n\nThere is an error with the tool!\n")
    traceback.print_exc()
示例#17
0
  def reset_state(self, old, new):
    """
    Function: reset_state
    ---------------------
    Resets the state of the database from 'new' back to 'old'. This involves
    removing all functions, views, functions, procedures, and triggers that
    have been newly created.

    old: The old state of the database to be reverted back to.
    new: The new (current) state of the database.
    """
    new.subtract(old)

    try:
      # Drop all functions procedures, and triggers first.
      # if VERBOSE:
      #   print("-" * 78)
      #   print("Resetting state.")
          
      for trig in new.triggers:
        sql = "DROP TRIGGER IF EXISTS %s" % trig
        # if VERBOSE:
        #   print(sql)
          
        self.execute_sql(sql)
        
      for proc in new.procedures:
        sql = "DROP PROCEDURE IF EXISTS %s" % proc
        # if VERBOSE:
        #   print(sql)
          
        self.execute_sql(sql)
        
      for func in new.functions:
        sql = "DROP FUNCTION IF EXISTS %s" % func
        
        # if VERBOSE:
        #   print(sql)
          
        self.execute_sql(sql)

      # Drop views.
      for view in new.views:
        sql = "DROP VIEW IF EXISTS %s" % view

        # if VERBOSE:
        #   print(sql)
        
        self.execute_sql(sql)

      # Drop tables. First must drop foreign keys on the tables in order to be
      # able to drop the tables without any errors.
      for (table, fk) in new.foreign_keys:
        self.execute_sql("ALTER TABLE %s DROP FOREIGN KEY %s" % (table, fk))
      for table in new.tables:
        self.execute_sql("DROP TABLE IF EXISTS %s" % table)
    except (mysql.connector.errors.Error, DatabaseError, TimeoutError):
      err("Could not reset database state. Possible errors in future grading.")

    # Remove all savepoints.
    self.savepoints = []