diff --git a/src/problem_bank_scripts/problem_bank_scripts.py b/src/problem_bank_scripts/problem_bank_scripts.py
index 0366c9ce..ae73ed01 100644
--- a/src/problem_bank_scripts/problem_bank_scripts.py
+++ b/src/problem_bank_scripts/problem_bank_scripts.py
@@ -457,6 +457,7 @@ def write_info_json(output_path, parsed_question):
"singleVariant",
"showCorrectAnswer",
"externalGradingOptions",
+ "workspaceOptions"
}
# Add tags based on part type
@@ -489,6 +490,15 @@ def write_info_json(output_path, parsed_question):
}
)
+ if "workspaceOptions" in info_json: # validate workspaceOptions contains the required keys if it exists
+ image = "image" in info_json["workspaceOptions"]
+ port = "port" in info_json["workspaceOptions"]
+ home = "home" in info_json["workspaceOptions"]
+ if not (image and port and home):
+ raise SyntaxError("workspaceOptions must contain image, port, and home keys")
+ if not isinstance(info_json["workspaceOptions"]["port"], int):
+ raise TypeError(f"workspaceOptions.port must be an integer, got {type(info_json['workspaceOptions']['port'])!r} instead")
+
# End add tags
with pathlib.Path(output_path / "info.json").open("w") as output_file:
json.dump(info_json, output_file, indent=4)
@@ -874,6 +884,31 @@ def process_string_input(part_name, parsed_question, data_dict):
return replace_tags(html)
+def process_workspace(part_name, parsed_question, data_dict):
+ """Processes markdown format of workspace questions and returns PL HTML
+ Args:
+ part_name (string): Name of the question part being processed (e.g., part1, part2, etc...)
+ parsed_question (dict): Dictionary of the MD-parsed question (output of `read_md_problem`)
+ data_dict (dict): Dictionary of the `data` dict created after running server.py using `exec()`
+
+ Returns:
+ html: A string of HTML that is part of the final PL question.html file.
+ """
+ if "pl-customizations" in parsed_question["header"][part_name]:
+ if len(parsed_question["header"][part_name]["pl-customizations"]) > 0:
+ raise ValueError("pl-customizations are not supported for workspace questions")
+
+
+ html = f"""\n{parsed_question['body_parts_split'][part_name]['content']}\n\n\n"""
+
+ html += f""""""
+
+ if parsed_question["header"][part_name].get("gradingMethod", None) == "External":
+ html += f"""\n\t\n\t"""
+
+ return replace_tags(html)
+
+
def process_matrix_component_input(part_name, parsed_question, data_dict):
"""Processes markdown format of matrix-component-input questions and returns PL HTML
Args:
@@ -1171,24 +1206,56 @@ def str_presenter(dumper, data2):
encoding="utf8",
)
- # Move image assets
+ # Create the file errors list
+ os_errors = []
+
+ # Move client assets (generally images)
files_to_copy = header.get("assets")
if files_to_copy:
- [
- copy2(pathlib.Path(source_filepath).parent / fl, output_path.parent)
- for fl in files_to_copy
- ]
+ pl_path = output_path.parent
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
+
+ # Move server assets
+ files_to_copy = header.get("serverFiles")
+ if files_to_copy and instructor:
+ pl_path = output_path.parent
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
# Move autograde py test files
files_to_copy = header.get("autogradeTestFiles")
if files_to_copy:
pl_path = output_path.parent / "tests"
pl_path.mkdir(parents=True, exist_ok=True)
- [
- copy2(pathlib.Path(source_filepath).parent / "tests" / fl, pl_path / fl)
- for fl in files_to_copy
- if (instructor or fl == "starter_code.py")
- ]
+ for file in files_to_copy:
+ if file != "starter_code.py" and not instructor:
+ continue
+ try:
+ copy2(pathlib.Path(source_filepath).parent / "tests" / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
+
+ # Move workspace files
+ files_to_copy = header.get("workspaceFiles")
+ if files_to_copy:
+ pl_path = output_path.parent / "workspace"
+ pl_path.mkdir(parents=True, exist_ok=True)
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / "workspace" / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
+
+ if os_errors:
+ error_msg = "\n ".join(os_errors)
+ raise FileNotFoundError(f"Error(s) copying specified files:\n {error_msg}")
def process_question_pl(source_filepath, output_path=None, dev=False):
@@ -1310,6 +1377,8 @@ def process_question_pl(source_filepath, output_path=None, dev=False):
question_html += process_string_input(part, parsed_q, data2)
elif "matching" in q_type:
question_html += process_matching(part, parsed_q, data2)
+ elif "workspace" in q_type:
+ question_html += process_workspace(part, parsed_q, data2)
elif "matrix-component-input" in q_type:
question_html += process_matrix_component_input(part, parsed_q, data2)
elif "matrix-input" in q_type:
@@ -1364,25 +1433,56 @@ def process_question_pl(source_filepath, output_path=None, dev=False):
# Write server.py file
write_server_py(output_path, parsed_q)
- # Move image assets
+ # Create the file errors list
+ os_errors = []
+
+ # Move client assets (generally images)
files_to_copy = parsed_q["header"].get("assets")
if files_to_copy:
pl_path = output_path / "clientFilesQuestion"
pl_path.mkdir(parents=True, exist_ok=True)
- [
- copy2(pathlib.Path(source_filepath).parent / fl, pl_path / fl)
- for fl in files_to_copy
- ]
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
+
+ # Move server assets
+ files_to_copy = parsed_q["header"].get("serverFiles")
+ if files_to_copy:
+ pl_path = output_path / "serverFilesQuestion"
+ pl_path.mkdir(parents=True, exist_ok=True)
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
# Move autograde py test files
files_to_copy = parsed_q["header"].get("autogradeTestFiles")
if files_to_copy:
pl_path = output_path / "tests"
pl_path.mkdir(parents=True, exist_ok=True)
- [
- copy2(pathlib.Path(source_filepath).parent / "tests" / fl, pl_path / fl)
- for fl in files_to_copy
- ]
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / "tests" / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
+
+ # Move workspace files
+ files_to_copy = parsed_q["header"].get("workspaceFiles")
+ if files_to_copy:
+ pl_path = output_path / "workspace"
+ pl_path.mkdir(parents=True, exist_ok=True)
+ for file in files_to_copy:
+ try:
+ copy2(pathlib.Path(source_filepath).parent / "workspace" / file, pl_path / file)
+ except (FileExistsError, FileNotFoundError, IsADirectoryError, PermissionError) as e:
+ os_errors.append(str(e))
+
+ if os_errors:
+ error_msg = "\n ".join(os_errors)
+ raise FileNotFoundError(f"Error(s) copying specified files:\n {error_msg}")
def pl_image_path(html):
@@ -1433,3 +1533,4 @@ def validate_header(header_dict):
if topics.get(topic := header_dict["topic"], None) is None:
raise ValueError(f"topic '{topic}' is not listed in the learning outcomes")
+
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/file1.txt b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/file2.txt b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/filebad.txt b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/q16_workspaces.md b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/q16_workspaces.md
new file mode 100644
index 00000000..a1e11c53
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/q16_workspaces.md
@@ -0,0 +1,153 @@
+---
+title: Moving and copying files
+topic: Template
+author: Jonatan Schroeder; Converted by Gavin Kendal-Freedman
+source: original
+template_version: 1.0
+attribution: standard
+partialCredit: true
+singleVariant: false
+showCorrectAnswer: false
+outcomes:
+- 6.1.1.0
+- 6.1.1.1
+difficulty:
+- undefined
+randomization:
+- undefined
+taxonomy:
+- undefined
+span:
+- undefined
+length:
+- undefined
+tags:
+- unknown
+assets: null
+workspaceFiles:
+- file1.txt
+- file2.txt
+- filebad.txt
+serverFiles:
+- file1.txt
+- file2.txt
+- filebad.txt
+workspaceOptions:
+ image: prairielearn/workspace-xtermjs
+ port: 8080
+ home: /home/student
+ gradedFiles:
+ - file1.txt
+ - file2.txt
+ - filebad.txt
+ - newdir/file1_new.txt
+ - newdir/file2_new.txt
+server:
+ imports: "import base64 \nimport problem_bank_helpers as pbh\n"
+ generate: |
+ data2 = pbh.create_data2()
+
+ data2["params"]["vars"]["title"] = "Moving and copying files"
+
+ # Update the data object with a new dict
+ data.update(data2)
+ grade: |
+ files = { f['name']: f['contents'] for f in data['submitted_answers'].get('_files', []) }
+ data['feedback']['results'] = []
+ data['score'] = 0
+
+ with open('workspace/file1.txt', 'rb') as f:
+ file1_content = base64.b64encode(f.read()).decode()
+ with open('workspace/file2.txt', 'rb') as f:
+ file2_content = base64.b64encode(f.read()).decode()
+
+ if 'newdir/file1_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was not created.')
+ elif files['newdir/file1_new.txt'] != file1_content:
+ data['feedback']['results'].append('newdir/file1_new.txt was created, but it is not based on file1.txt.')
+ elif 'file1.txt' in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was created properly, but the old file still exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file1.txt moved successfully.')
+ data['score'] += 0.4
+
+ if 'newdir/file2_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was not created.')
+ elif files['newdir/file2_new.txt'] != file2_content:
+ data['feedback']['results'].append('newdir/file2_new.txt was created, but it is not based on file2.txt.')
+ elif 'file2.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was created properly, but the old file no longer exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file2.txt copied successfully.')
+ data['score'] += 0.4
+
+ if files and 'filebad.txt' in files:
+ data['feedback']['results'].append('filebad.txt still exists.')
+ else:
+ data['feedback']['results'].append('filebad.txt deleted successfully.')
+ data['score'] += 0.2
+ prepare: 'pass
+
+ '
+ parse: |
+ if '_files' in data['format_errors']:
+ del data['format_errors']['_files']
+part1:
+ type: workspace
+myst:
+ substitutions:
+ params:
+ vars:
+ title: Moving and copying files
+
+---
+# {{ params.vars.title }}
+
+## Question Text
+
+Open the Workspace below and perform the following tasks.
+
+- Create a new directory called `newdir`;
+- Move the file `file1.txt` to the directory `newdir`, with the new name `file1_new.txt`;
+- Copy the file `file2.txt` to the directory `newdir`, with the new name `file2_new.txt`;
+- Delete the file `filebad.txt`.
+
+Once you complete these tasks, click on "Save & Grade" below.
+
+### Answer Section
+
+### pl-submission-panel
+
+
+ {{#feedback.results}}
+ - {{.}}
+ {{/feedback.results}}
+
+
+
+
+### pl-answer-panel
+
+Everything here will get inserted directly into an pl-answer-panel element at the end of the `question.html`.
+Please remove this section if it is not application for this question.
+
+## Rubric
+
+This should be hidden from students until after the deadline.
+
+## Solution
+
+This should never be revealed to students.
+
+## Comments
+
+These are random comments associated with this question:
+
+This example problem was originally created by [Jonatan Schroeder](https://www.cs.ubc.ca/people/jonatan-schroeder), and was later converted to the OER markdown layout.
+
+## Attribution
+
+Problem is licensed under the [CC-BY-NC-SA 4.0 license](https://creativecommons.org/licenses/by-nc-sa/4.0/).

\ No newline at end of file
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/file1.txt b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/file2.txt b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/filebad.txt b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/instructor/q16_workspaces/workspace/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/info.json b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/info.json
new file mode 100644
index 00000000..221ffeb9
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/info.json
@@ -0,0 +1,25 @@
+{
+ "uuid": "fc0d8086-108a-3418-a5d7-498e9af0bd74",
+ "title": "Moving and copying files",
+ "topic": "000.Template",
+ "tags": [
+ "workspace",
+ "DEV"
+ ],
+ "type": "v3",
+ "singleVariant": false,
+ "partialCredit": true,
+ "workspaceOptions": {
+ "image": "prairielearn/workspace-xtermjs",
+ "port": 8080,
+ "home": "/home/student",
+ "gradedFiles": [
+ "file1.txt",
+ "file2.txt",
+ "filebad.txt",
+ "newdir/file1_new.txt",
+ "newdir/file2_new.txt"
+ ]
+ },
+ "showCorrectAnswer": false
+}
\ No newline at end of file
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/question.html b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/question.html
new file mode 100644
index 00000000..83e23818
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/question.html
@@ -0,0 +1,38 @@
+
+
+
+
+Open the Workspace below and perform the following tasks.
+
+- Create a new directory called `newdir`;
+- Move the file `file1.txt` to the directory `newdir`, with the new name `file1_new.txt`;
+- Copy the file `file2.txt` to the directory `newdir`, with the new name `file2_new.txt`;
+- Delete the file `filebad.txt`.
+
+Once you complete these tasks, click on "Save & Grade" below.
+
+
+
+
+
+ {{#feedback.results}}
+ - {{.}}
+ {{/feedback.results}}
+
+
+
+
+
+Everything here will get inserted directly into an pl-answer-panel element at the end of the `question.html`.
+Please remove this section if it is not application for this question.
+
+
+
+
+
+
+---
+Problem is licensed under the [CC-BY-NC-SA 4.0 license](https://creativecommons.org/licenses/by-nc-sa/4.0/).

+
+
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/server.py b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/server.py
new file mode 100644
index 00000000..1e664476
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/server.py
@@ -0,0 +1,70 @@
+import base64
+import problem_bank_helpers as pbh
+
+def imports(data):
+ import base64
+ import problem_bank_helpers as pbh
+
+def generate(data):
+ data2 = pbh.create_data2()
+
+ data2["params"]["vars"]["title"] = "Moving and copying files"
+
+ # Update the data object with a new dict
+ data.update(data2)
+
+ # Start code added automatically by problem_bank_scripts
+
+ # Convert backticks to code blocks/fences in answer choices.
+ pbh.backticks_to_code_tags(data2)
+
+ # Update data with data2
+ data.update(data2)
+
+ # End code added in by problem bank scripts
+
+def grade(data):
+ files = { f['name']: f['contents'] for f in data['submitted_answers'].get('_files', []) }
+ data['feedback']['results'] = []
+ data['score'] = 0
+
+ with open('workspace/file1.txt', 'rb') as f:
+ file1_content = base64.b64encode(f.read()).decode()
+ with open('workspace/file2.txt', 'rb') as f:
+ file2_content = base64.b64encode(f.read()).decode()
+
+ if 'newdir/file1_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was not created.')
+ elif files['newdir/file1_new.txt'] != file1_content:
+ data['feedback']['results'].append('newdir/file1_new.txt was created, but it is not based on file1.txt.')
+ elif 'file1.txt' in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was created properly, but the old file still exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file1.txt moved successfully.')
+ data['score'] += 0.4
+
+ if 'newdir/file2_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was not created.')
+ elif files['newdir/file2_new.txt'] != file2_content:
+ data['feedback']['results'].append('newdir/file2_new.txt was created, but it is not based on file2.txt.')
+ elif 'file2.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was created properly, but the old file no longer exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file2.txt copied successfully.')
+ data['score'] += 0.4
+
+ if files and 'filebad.txt' in files:
+ data['feedback']['results'].append('filebad.txt still exists.')
+ else:
+ data['feedback']['results'].append('filebad.txt deleted successfully.')
+ data['score'] += 0.2
+
+def prepare(data):
+ pass
+
+def parse(data):
+ if '_files' in data['format_errors']:
+ del data['format_errors']['_files']
+
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/file1.txt b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/file2.txt b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/filebad.txt b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/serverFilesQuestion/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/file1.txt b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/file2.txt b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/filebad.txt b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn-dev/q16_workspaces/workspace/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/info.json b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/info.json
new file mode 100644
index 00000000..b2bc7872
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/info.json
@@ -0,0 +1,24 @@
+{
+ "uuid": "fad3fc60-3738-3899-8c8d-04a0bf0a5c70",
+ "title": "Moving and copying files",
+ "topic": "000.Template",
+ "tags": [
+ "workspace"
+ ],
+ "type": "v3",
+ "singleVariant": false,
+ "partialCredit": true,
+ "workspaceOptions": {
+ "image": "prairielearn/workspace-xtermjs",
+ "port": 8080,
+ "home": "/home/student",
+ "gradedFiles": [
+ "file1.txt",
+ "file2.txt",
+ "filebad.txt",
+ "newdir/file1_new.txt",
+ "newdir/file2_new.txt"
+ ]
+ },
+ "showCorrectAnswer": false
+}
\ No newline at end of file
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/question.html b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/question.html
new file mode 100644
index 00000000..83e23818
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/question.html
@@ -0,0 +1,38 @@
+
+
+
+
+Open the Workspace below and perform the following tasks.
+
+- Create a new directory called `newdir`;
+- Move the file `file1.txt` to the directory `newdir`, with the new name `file1_new.txt`;
+- Copy the file `file2.txt` to the directory `newdir`, with the new name `file2_new.txt`;
+- Delete the file `filebad.txt`.
+
+Once you complete these tasks, click on "Save & Grade" below.
+
+
+
+
+
+ {{#feedback.results}}
+ - {{.}}
+ {{/feedback.results}}
+
+
+
+
+
+Everything here will get inserted directly into an pl-answer-panel element at the end of the `question.html`.
+Please remove this section if it is not application for this question.
+
+
+
+
+
+
+---
+Problem is licensed under the [CC-BY-NC-SA 4.0 license](https://creativecommons.org/licenses/by-nc-sa/4.0/).

+
+
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/server.py b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/server.py
new file mode 100644
index 00000000..1e664476
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/server.py
@@ -0,0 +1,70 @@
+import base64
+import problem_bank_helpers as pbh
+
+def imports(data):
+ import base64
+ import problem_bank_helpers as pbh
+
+def generate(data):
+ data2 = pbh.create_data2()
+
+ data2["params"]["vars"]["title"] = "Moving and copying files"
+
+ # Update the data object with a new dict
+ data.update(data2)
+
+ # Start code added automatically by problem_bank_scripts
+
+ # Convert backticks to code blocks/fences in answer choices.
+ pbh.backticks_to_code_tags(data2)
+
+ # Update data with data2
+ data.update(data2)
+
+ # End code added in by problem bank scripts
+
+def grade(data):
+ files = { f['name']: f['contents'] for f in data['submitted_answers'].get('_files', []) }
+ data['feedback']['results'] = []
+ data['score'] = 0
+
+ with open('workspace/file1.txt', 'rb') as f:
+ file1_content = base64.b64encode(f.read()).decode()
+ with open('workspace/file2.txt', 'rb') as f:
+ file2_content = base64.b64encode(f.read()).decode()
+
+ if 'newdir/file1_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was not created.')
+ elif files['newdir/file1_new.txt'] != file1_content:
+ data['feedback']['results'].append('newdir/file1_new.txt was created, but it is not based on file1.txt.')
+ elif 'file1.txt' in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was created properly, but the old file still exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file1.txt moved successfully.')
+ data['score'] += 0.4
+
+ if 'newdir/file2_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was not created.')
+ elif files['newdir/file2_new.txt'] != file2_content:
+ data['feedback']['results'].append('newdir/file2_new.txt was created, but it is not based on file2.txt.')
+ elif 'file2.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was created properly, but the old file no longer exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file2.txt copied successfully.')
+ data['score'] += 0.4
+
+ if files and 'filebad.txt' in files:
+ data['feedback']['results'].append('filebad.txt still exists.')
+ else:
+ data['feedback']['results'].append('filebad.txt deleted successfully.')
+ data['score'] += 0.2
+
+def prepare(data):
+ pass
+
+def parse(data):
+ if '_files' in data['format_errors']:
+ del data['format_errors']['_files']
+
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/file1.txt b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/file2.txt b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/filebad.txt b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/serverFilesQuestion/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/file1.txt b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/file2.txt b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/filebad.txt b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/prairielearn/q16_workspaces/workspace/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/q16_workspaces.md b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/q16_workspaces.md
new file mode 100644
index 00000000..8d63d498
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/q16_workspaces.md
@@ -0,0 +1,84 @@
+---
+title: Moving and copying files
+topic: Template
+author: Jonatan Schroeder; Converted by Gavin Kendal-Freedman
+source: original
+template_version: 1.0
+attribution: standard
+partialCredit: true
+singleVariant: false
+showCorrectAnswer: false
+outcomes:
+- 6.1.1.0
+- 6.1.1.1
+difficulty:
+- undefined
+randomization:
+- undefined
+taxonomy:
+- undefined
+span:
+- undefined
+length:
+- undefined
+tags:
+- unknown
+assets: null
+workspaceFiles:
+- file1.txt
+- file2.txt
+- filebad.txt
+serverFiles:
+- file1.txt
+- file2.txt
+- filebad.txt
+workspaceOptions:
+ image: prairielearn/workspace-xtermjs
+ port: 8080
+ home: /home/student
+ gradedFiles:
+ - file1.txt
+ - file2.txt
+ - filebad.txt
+ - newdir/file1_new.txt
+ - newdir/file2_new.txt
+part1:
+ type: workspace
+myst:
+ substitutions:
+ params_vars_title: Moving and copying files
+---
+# {{ params_vars_title }}
+
+## Question Text
+
+Open the Workspace below and perform the following tasks.
+
+- Create a new directory called `newdir`;
+- Move the file `file1.txt` to the directory `newdir`, with the new name `file1_new.txt`;
+- Copy the file `file2.txt` to the directory `newdir`, with the new name `file2_new.txt`;
+- Delete the file `filebad.txt`.
+
+Once you complete these tasks, click on "Save & Grade" below.
+
+### Answer Section
+
+### pl-submission-panel
+
+
+ {{#feedback.results}}
+ - {{.}}
+ {{/feedback.results}}
+
+
+
+
+### pl-answer-panel
+
+Everything here will get inserted directly into an pl-answer-panel element at the end of the `question.html`.
+Please remove this section if it is not application for this question.
+
+## Attribution
+
+Problem is licensed under the [CC-BY-NC-SA 4.0 license](https://creativecommons.org/licenses/by-nc-sa/4.0/).

\ No newline at end of file
diff --git a/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/file1.txt b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/file2.txt b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/filebad.txt b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_expected_outputs/public/q16_workspaces/workspace/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/file1.txt b/tests/test_question_templates/question_inputs/q16_workspaces/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/file2.txt b/tests/test_question_templates/question_inputs/q16_workspaces/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/filebad.txt b/tests/test_question_templates/question_inputs/q16_workspaces/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/q16_workspaces.md b/tests/test_question_templates/question_inputs/q16_workspaces/q16_workspaces.md
new file mode 100644
index 00000000..24fa812f
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/q16_workspaces.md
@@ -0,0 +1,146 @@
+---
+title: Moving and copying files
+topic: Template
+author: Jonatan Schroeder; Converted by Gavin Kendal-Freedman
+source: original
+template_version: 1.0
+attribution: standard
+partialCredit: true
+singleVariant: false
+showCorrectAnswer: false
+outcomes:
+- 6.1.1.0
+- 6.1.1.1
+difficulty:
+- undefined
+randomization:
+- undefined
+taxonomy:
+- undefined
+span:
+- undefined
+length:
+- undefined
+tags:
+- unknown
+assets:
+workspaceFiles:
+ - file1.txt
+ - file2.txt
+ - filebad.txt
+serverFiles:
+ - file1.txt
+ - file2.txt
+ - filebad.txt
+workspaceOptions:
+ image: prairielearn/workspace-xtermjs
+ port: 8080
+ home: /home/student
+ gradedFiles:
+ - file1.txt
+ - file2.txt
+ - filebad.txt
+ - newdir/file1_new.txt
+ - newdir/file2_new.txt
+server:
+ imports: |
+ import base64
+ import problem_bank_helpers as pbh
+ generate: |
+ data2 = pbh.create_data2()
+
+ data2["params"]["vars"]["title"] = "Moving and copying files"
+
+ # Update the data object with a new dict
+ data.update(data2)
+ grade: |
+ files = { f['name']: f['contents'] for f in data['submitted_answers'].get('_files', []) }
+ data['feedback']['results'] = []
+ data['score'] = 0
+
+ with open('workspace/file1.txt', 'rb') as f:
+ file1_content = base64.b64encode(f.read()).decode()
+ with open('workspace/file2.txt', 'rb') as f:
+ file2_content = base64.b64encode(f.read()).decode()
+
+ if 'newdir/file1_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was not created.')
+ elif files['newdir/file1_new.txt'] != file1_content:
+ data['feedback']['results'].append('newdir/file1_new.txt was created, but it is not based on file1.txt.')
+ elif 'file1.txt' in files:
+ data['feedback']['results'].append('newdir/file1_new.txt was created properly, but the old file still exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file1.txt moved successfully.')
+ data['score'] += 0.4
+
+ if 'newdir/file2_new.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was not created.')
+ elif files['newdir/file2_new.txt'] != file2_content:
+ data['feedback']['results'].append('newdir/file2_new.txt was created, but it is not based on file2.txt.')
+ elif 'file2.txt' not in files:
+ data['feedback']['results'].append('newdir/file2_new.txt was created properly, but the old file no longer exists.')
+ data['score'] += 0.2
+ else:
+ data['feedback']['results'].append('file2.txt copied successfully.')
+ data['score'] += 0.4
+
+ if files and 'filebad.txt' in files:
+ data['feedback']['results'].append('filebad.txt still exists.')
+ else:
+ data['feedback']['results'].append('filebad.txt deleted successfully.')
+ data['score'] += 0.2
+ prepare: |
+ pass
+ parse: |
+ if '_files' in data['format_errors']:
+ del data['format_errors']['_files']
+part1:
+ type: workspace
+---
+# {{ params.vars.title }}
+
+## Question Text
+
+Open the Workspace below and perform the following tasks.
+
+- Create a new directory called `newdir`;
+- Move the file `file1.txt` to the directory `newdir`, with the new name `file1_new.txt`;
+- Copy the file `file2.txt` to the directory `newdir`, with the new name `file2_new.txt`;
+- Delete the file `filebad.txt`.
+
+Once you complete these tasks, click on "Save & Grade" below.
+
+### Answer Section
+
+
+### pl-submission-panel
+
+
+ {{#feedback.results}}
+ - {{.}}
+ {{/feedback.results}}
+
+
+
+
+### pl-answer-panel
+
+Everything here will get inserted directly into an pl-answer-panel element at the end of the `question.html`.
+Please remove this section if it is not application for this question.
+
+## Rubric
+
+This should be hidden from students until after the deadline.
+
+## Solution
+
+This should never be revealed to students.
+
+## Comments
+
+These are random comments associated with this question:
+
+This example problem was originally created by [Jonatan Schroeder](https://www.cs.ubc.ca/people/jonatan-schroeder), and was later converted to the OER markdown layout.
+
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/workspace/file1.txt b/tests/test_question_templates/question_inputs/q16_workspaces/workspace/file1.txt
new file mode 100644
index 00000000..b30565ee
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/workspace/file1.txt
@@ -0,0 +1,18 @@
+Gk44G5S2cov8PgPpy9n7r0v4FYY7zU/7i8UNDrRYLU1GjpVdCPIzdbUUcln3CqNlBTEKjn75+3qs
+DUvteohtwFF+4BYwAyfv7yjzX9CPsyfHjBYC+b+fMHPMFns1vdlGUvwDEePE4k1EnJECUZKTkebJ
+ymKhR6BY4IUnO5cVsRARExtVkYddHUcNAhgLpN9E/SNln9gJ9fN4WRat6l2QSDaH2OJ96+lDM/yR
+EVttiFtCynzgguVYQy06tlpLXk9a9MwPlVGJvH/qH28pHkIac2H6UpWnDcBptsVJTVfTVv9Gp9/s
+IQmeZRDIUy6163Ga9Uke6o1W7dVeohjvwY/KQHApZcK15RDkb+UiBd/9VM26N7eMoW41Sg7SKtQZ
++i8vEu2H6whEnvZCnYg+sOh9fxaDlOQhh7qhu4g2z1TORCjWzgELzZEmPJVrsf45cG0tz0xKoF/d
+Uw/YoHF8RJexASnLZWj4125sNCQ6XfoEl1DLzUULxH7SeWM9owpouV5RZC3LZmCgyDUvbI0oMk+l
+9GTae6lcMLOKD1R2q4NksQAt/osyWXQZw1g6pcqNSz1TdirbiQsTY6g+JZ3pYN2tK2w4WqVnLPix
+lI9ktBec6XknnEEvrI7NhPLEXdxS7mUXuapCV6EfMfDPMvW523EUQPE5MWYBvHTmlIW5pemWgsAd
+vuCFOIjs73L0es+88D3gCrSGSyk7KydSY3f2XCD8PLf78ZamVWi9yRpQryc33bRB2CaGhHhmbKto
+4nsb+ToaG0aZE2FtIjOHTyC8jjsqgLRbHfRXJVFnngKVT+DEQnrfLRuC8+PtMrDDpZ4VarpK0aLd
+BMtHPOsg+cOhwhqU021s2nq6G6XKfSDHeRR/2T/R0loeMr8/6UCVQKXitWl2f//tfnBYYMQObys3
+tYsJC46pR+Gp9ytUxc+//NKKmhn1J4Sm8anhGeePEmxroVbm5O16jk62u+L2jNJ0HOGYXNKizuKP
+aVSReke4ZqdxJCCLZjLS6q4B5v5s7XK2TRFUb7BUeOVvezG698khGjCdPDOZH1gYxRoKR5VrkM1z
+Mr8VPMi6io9GhaGehNz+N5ygXp6IG9VOht+j7H1y76rgooUtmtNKLuySTkf4nxK/FocuyCsDxP0r
+jIZIgLEn1CD6R+ewbVZbFqwJt5RfJRpGss3Lmn3SdF4VEYQNHos6ytL0JzejC0Q7Foy5YC0cC0QC
+fjpFZcbNXCJSnq6fX+KKq8MdHdivisU2Nd+5TCeqDdpuz2Plxs0o6wMlrYxIJKH+caxzvtlKdmRh
+u1Ko/xz1A4VkljPJAudnrWXuvgfxVCacQ0DNJ/I8fA==
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/workspace/file2.txt b/tests/test_question_templates/question_inputs/q16_workspaces/workspace/file2.txt
new file mode 100644
index 00000000..492987ca
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/workspace/file2.txt
@@ -0,0 +1,18 @@
+DBVUxxDUlMoztjq4PiJSvgtwoY56pePoFur5Pz64DnE3tf2d7ffUtDBqluysfK62jpBzD9Ws6sTp
+HZj7GIV1UQZloqbT2jPwyi5HGs1/aDfwTJA+ISur9mSEKLGEHADFqMsC9B5OW1P6hpzWiIjTGzde
+VBOE5LfCy03VEsHfu90Ouk+PjEkxCz7rdIjjmxIDTp0GVmy73UwjvPT9Qwu70i2qSA1MDpCfAFmu
+daTha8axoUcTnhNFlT2jVf4e/4cITNEsi/T/mMbpAtbXQXXLfflv7btJDTnY7L05AJetLmIXhZXB
+OCwkA0d4gPMjG6p5Lkq3Vs3aeO8CVgSLeKEWz8Pru1pbSe0xDtt6EGZXK4axQ/gpZQfymEdqF4x5
+V3qqlVMDYcwIHkZiLdmxZOBX6J62yHtU7VdMD8zaT2Ds+fnqc4O0kRuUl+wZb+aQ1+ScZ17mxZ4B
+6//1Ped29UUHuZQcJlOfrbtoXH4ZNO01OzZbQQ9YHlHLpOKQNqPXyBBH0bTz53ta5QX0ItMZ42eo
+61fMjWm4baU4UmB1s9jlQGMYoVoo8URROfuZn7kirS9J5YLOpAuny+dBZsS8ezjwFput3Hh5JyYo
+qIiC7oIVjHsHoYgAELxxFzLR3RreRxbKcoNxvdBu/4RngKzahXypIVPx0ldMiWIVnoEZlJtq5WE5
+S49N+ojExqOOWPPgXxcUP8dHkhfXoDgTph4M0ZFb4H6FJuwX/R7CqNHnqHbgYiCj1tZG4LBwVO/v
+kamJudzm/SBvLlUycL9JrMgedLnUmVZorUgj4IVDju8lyHcz8RRBFHCBTh4LII+W+PBQ21LBCsH8
+2BZPOh8MLXFb8G7stl/n0rMkqC+tmRw2CAjwmbz/54nwuzm8o7k7cGXhou6blkx6tBlC54muSBWd
+X7vNSctuY+JwwgLXk5JfeHIF2JdaCNaGyyBFgPer2rTG9PuoHD3QRibXOJSHy+NlCLrdrZXB89HQ
+WNCpeBjWjlclG/O7LL2yuvrkvM5eohPjRBIHa4lxzEecL5Y+SPUOy7vIg0DChpL5CzpbYzgM15mS
+Zd/ai7rkBzfdJMlmxTXNewMVaf4drtx9Cg9HJAWCjIjaX1lqOfku2vqKetfLQytz1qnNYo8oPcKE
+PmaHbGkubdHI3+dDbqva3LWgrKjUNdVaQq+ICo57Pdo1FycScSyWW58Peldh0mIZodxVt8IiDiR3
+hjVJqGkRuxT0T07Dc2fXHKtylz8L2VdAQeeqqdaLvJnNVRU/CRzjdLLnWKUzC4f3CiFVeHW1MiRn
+hWLsmy+tcEvZnFh29K1Ei55NFBcQjUvBQvmZgg+SiQ==
diff --git a/tests/test_question_templates/question_inputs/q16_workspaces/workspace/filebad.txt b/tests/test_question_templates/question_inputs/q16_workspaces/workspace/filebad.txt
new file mode 100644
index 00000000..90505446
--- /dev/null
+++ b/tests/test_question_templates/question_inputs/q16_workspaces/workspace/filebad.txt
@@ -0,0 +1,18 @@
+a5HQSUh3ZVyvZxZvF2t+Pi3BLjHQ8MTJJ/2I7rqOQQ+mPC3AWAD5WHR5OZ+XwE6qfVjThv0rH1Lk
+3N9phhpM+4Ij7g2KwPK6sb076LbD6PRVNpIVjEfEuzdi8pcZ4hDvYbPOSZ3FSpqvDGbFCBRSobSp
+npEnw2DkyABjlvh5JKsv732jrZBBXHqgtBE7RgVeT+wlXo6nu8h1330fOYQ3y0F8UCWELtqfHseU
+A3T/yB56wOG33k8jBvpAzAYmM+zr3xfFKhp36Ak2NvKH2oikeUi5wB1q8kaVVbHKCEoCYYW/mYnf
+FFaAh2zMYOhVEeNUaee8RrSQvIXar2vgOim9W4vJxgKRUpjp09YFDw9lOgu4ddru60pR7QLNmCP5
+ht1jImO1p0TZ+A/Vwp6jKskDFqKy7ZZBTHYT99scrUWd7OEyyIbWF7OSIeq8wubAqcbAJ6NMUBRv
+JqPxsbNNhkm/Xw4zvhQyhsI5P9QIjBpzL8SwxdhD5DdGfP8JGQLaLlvW8GlgBhteNY2k5lpSL2YJ
+UsT4BT6fkeZnaWNfxJR69eAI98PeDiVW8KedvtE/wU4WlhmXi/bY7lenZi6sBSzqiXxaJujszLIG
+c/bahugWAMXEBHGBduaYBOI6JGdFY2219qqKlY1aM2WUBTh/IF+Ig9PZDSMGW/Qo4HEo8HYge1sl
+JkbcIL2zNhLpUJR8eCaZ06QI8KOa2LvtVLRBFRCdLXhdelD3uZDYWRVh+0ykASdCNURqayIxC3ys
+Uwq95Qt5uB0teliY/xQ+JRvu6YnU+kStJGrEgaeS8ZjF85ogFCbNUnqvub1mvek/y23Z5SSYSKm/
+KcUpAIVfAg+W2YriERzUEsyzMjTKS1MP4mEeWUGgBIR4yGU2hivyd4P0eeIAti/zCTaKQBTRwXJX
+4nar06DgLUj4HHdcd7aaLTxt4bHA646IXO+2qYxQAZxvKkgWfZ6QWFakXVROR/6lfqJDLD3QVFD0
+0d25EOhPbxe/Q7L8TR7rMsmE7cpJvA/Mjv36g6f2l0PngzPlm/EiJ0i0wn/3Al4A5+AEfxDwRMFo
+rNkaJEyEkRBDo5UeVdnTVXH1z70RLSju0n4oJe3NS47nv+gP5brMCkPxEh0pY1vtSFskaLHqJZwV
+NWFSdij8THFL0dlfKG5wyPwtwCuQqCz029wRjfJNFEgfqWOGGXZLLC+7n4wdSr2QO+ppZdKqB/Bl
+t6i2u5iwH+XrYf4j9THtXCQ6w/FEere2q8EBkCaTSOhKC+Gyp1rAnyuuYIy/dxprJKI4EzhgVHYo
+171ZFv6aNT1ETGgB8ejaU9025ozLjRHvcbuHIZsm3g==