|
1 | | -# How to create an interactive Go lab? |
| 1 | +# How to create interactive Go lab? |
2 | 2 |
|
3 | | -Coming soon |
| 3 | +<!--@include: ./../_components/TechnologyIntro.md--> |
| 4 | + |
| 5 | +We'll divide this part into 5 sections: |
| 6 | + |
| 7 | +1. Creating lab metadata |
| 8 | +2. Setting up lab defaults |
| 9 | +3. Setting up lab challenges |
| 10 | +4. Setting up evaluation script |
| 11 | +5. Setting up test file |
| 12 | + |
| 13 | +## Introduction |
| 14 | + |
| 15 | +This guide would assume that you already have created an interactive course from your instructor panel. If not, [go here and set it up first](https://codedamn.com/instructor/interactive-courses) |
| 16 | + |
| 17 | +## Step 1 - Creating lab metadata |
| 18 | + |
| 19 | +<!--@include: ./../_components/LabMetadata.md--> |
| 20 | + |
| 21 | +### Lab Details |
| 22 | + |
| 23 | +Lab details is the tab where you add two important things: |
| 24 | + |
| 25 | +- Lab title |
| 26 | +- Lab description |
| 27 | + |
| 28 | +Once both of them are filled, it would appear as following: |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +Let's move to the next tab now. |
| 33 | + |
| 34 | +### Container Image |
| 35 | + |
| 36 | +Container image should be set as Golang. Container image is a hint for us to know ahead of time what the primary language of your lab would be. |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | +### Lab Layout |
| 41 | + |
| 42 | +<!--@include: ./../_components/LabLayout.md--> |
| 43 | + |
| 44 | +## Step 2 - Lab Defaults |
| 45 | + |
| 46 | +Lab defaults section include how your lab environment boots. It is one of the most important parts because a wrong default environment might confuse your students. Therefore it is important to set it up properly. |
| 47 | + |
| 48 | +When a codedamn playground boots, it can setup a filesystem for user by default. You can specify what the starting files could be, by specifying a git repository and a branch name: |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +:::info |
| 53 | +You will find a `.cdmrc` file in the repository given to you above. It is highly recommend, at this point, that you go through the [.cdmrc guide and how to use .cdmrc in playgrounds](/docs/concepts/cdmrc) to understand what `.cdmrc` file exactly is. Once you understand how to work with `.cdmrc` come back to this area. |
| 54 | +::: |
| 55 | + |
| 56 | +## Step 3 - Lab challenges |
| 57 | + |
| 58 | +<!--@include: ./../_components/LabChallenges.md--> |
| 59 | + |
| 60 | +## Step 4 - Evaluation Script |
| 61 | + |
| 62 | +Evaluation script is actually what runs when the user on the codedamn playground clicks on "Run Tests" button. |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +Since we have already written a pure evaluation script that runs and finally writes the JSON result to the `UNIT_TEST_OUTPUT_FILE` environment, all we have to do is trigger that script via Node.js. |
| 67 | + |
| 68 | +The full path of the script is made available at run-time with another environment variable called `TEST_FILE_NAME`. Therefore, all we have to do is write the following in the evaluation script area: |
| 69 | + |
| 70 | +```sh |
| 71 | +node $TEST_FILE_NAME |
| 72 | +``` |
| 73 | + |
| 74 | +This will make sure we run the full Node.js script and write the results properly for the playground IDE to read. It would look like the following: |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +**Note:** You can setup a full testing environment in this block of evaluation script (installing more packages, etc. if you want). However, your test file will be timed out **after 30 seconds**. Therefore, make sure, all of your testing can happen within 30 seconds. |
| 79 | + |
| 80 | +## Step 5 - Test file |
| 81 | + |
| 82 | +You will see a button named `Edit Test File` in the `Evaluation` tab. Click on it. |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +When you click on it, a new window will open. This is a test file area. |
| 87 | + |
| 88 | +You can write anything here. Whatever script you write here, can be executed from the `Test command to run section` inside the evaluation tab we were in earlier. |
| 89 | + |
| 90 | +The point of having a file like this to provide you with a place where you can write your evaluation script. |
| 91 | + |
| 92 | +**For HTML/CSS labs, you can use the default test file of Node.js (Puppeteer) evaluation:** |
| 93 | + |
| 94 | + |
| 95 | + |
| 96 | +The moment you select the Node.js (Puppeteer), the following code should appear in your editor: |
| 97 | + |
| 98 | +```js |
| 99 | +// !! Boilerplate code starts |
| 100 | +const fs = require('fs') |
| 101 | +const puppeteer = require('puppeteer') |
| 102 | + |
| 103 | +async function run() { |
| 104 | + // results is a boolean[] that maps challenge results shown to user |
| 105 | + const results = [] |
| 106 | + |
| 107 | + // launch the headless browser for testing |
| 108 | + const browser = await puppeteer.launch({ |
| 109 | + executablePath: '/usr/bin/google-chrome', |
| 110 | + headless: true, |
| 111 | + args: [ |
| 112 | + '--no-sandbox', |
| 113 | + '--disable-setuid-sandbox', |
| 114 | + '--disable-dev-shm-usage', |
| 115 | + '--disable-accelerated-2d-canvas', |
| 116 | + '--no-first-run', |
| 117 | + '--no-zygote', |
| 118 | + '--single-process', |
| 119 | + '--disable-gpu', |
| 120 | + ], |
| 121 | + }) |
| 122 | + page = await browser.newPage() |
| 123 | + |
| 124 | + // wait for server to come online |
| 125 | + await page.goto('http://localhost:1337') |
| 126 | + |
| 127 | + // add jQuery and chai for unit testing support if you want |
| 128 | + await Promise.all([ |
| 129 | + page.addScriptTag({ |
| 130 | + url: 'https://code.jquery.com/jquery-3.5.1.slim.min.js', |
| 131 | + }), |
| 132 | + page.addScriptTag({ |
| 133 | + url: 'https://cdnjs.cloudflare.com/ajax/libs/chai/4.2.0/chai.min.js', |
| 134 | + }), |
| 135 | + ]) |
| 136 | + |
| 137 | + // !! Boilerplate code ends |
| 138 | + |
| 139 | + // Start your tests here in individual try-catch block |
| 140 | + |
| 141 | + try { |
| 142 | + await page.evaluate(async () => { |
| 143 | + const assert = window.chai.assert |
| 144 | + assert( |
| 145 | + document.body.innerHTML.toLowerCase().includes('hello world') |
| 146 | + ) |
| 147 | + }) |
| 148 | + console.log('Test #1 passed!') |
| 149 | + results.push(true) |
| 150 | + } catch (error) { |
| 151 | + console.log('Test #1 failed! Did you do <this>?') |
| 152 | + results.push(false) |
| 153 | + } |
| 154 | + |
| 155 | + try { |
| 156 | + await page.evaluate(async () => { |
| 157 | + const assert = window.chai.assert |
| 158 | + assert( |
| 159 | + document.body.innerHTML |
| 160 | + .toLowerCase() |
| 161 | + .includes('hello world again') |
| 162 | + ) |
| 163 | + }) |
| 164 | + console.log('Test #1 passed!') |
| 165 | + results.push(true) |
| 166 | + } catch (error) { |
| 167 | + console.log('Test #1 failed! Did you do <this>?') |
| 168 | + results.push(false) |
| 169 | + } |
| 170 | + |
| 171 | + // End your tests here |
| 172 | + fs.writeFileSync(process.env.UNIT_TEST_OUTPUT_FILE, JSON.stringify(results)) |
| 173 | + await browser.close().catch((err) => {}) |
| 174 | + |
| 175 | + // Exit the process |
| 176 | + process.exit(0) |
| 177 | +} |
| 178 | +run() |
| 179 | +// !! Boilerplate code ends |
| 180 | +``` |
| 181 | + |
| 182 | +Let us understand what is happening here exactly: |
| 183 | + |
| 184 | +- Remember that we can code anything in this file and then execute it later. In this example, we're writing a Node.js script from scratch. |
| 185 | +- Remember that we already have puppeteer with headless chrome pre-installed in codedamn playgrounds for HTML/CSS. Therefore, we can import it directly. |
| 186 | +- In the first part of `run` function, we start a headless puppeteer browser. |
| 187 | +- We then visit `http://localhost:1337`. At this point, I would highly recommend you to read [How port mapping works for codedamn playgrounds](/docs/concepts/port-mapping), if you haven't yet. |
| 188 | +- From this point onwards, we have some `try-catch` blocks. But why? Because we want to populate an array `results` and then finally write this array to a file inside environment variable `UNIT_TEST_OUTPUT_FILE` |
| 189 | +- Let's say, `[true, false]` is written to the file `process.env.UNIT_TEST_OUTPUT_FILE`. In that case, the first challenge would be marked as passed in the IDE, and the second challenge would be marked as failed: |
| 190 | + |
| 191 | + |
| 192 | + |
| 193 | +- Whatever your mapping of final JSON boolean array written in `process.env.UNIT_TEST_OUTPUT_FILE` is, it is matched exactly to the results on the playground. For example, if the array written is `[true, false, true, true]`, the following would be the output on playground: |
| 194 | + |
| 195 | + |
| 196 | + |
| 197 | +- **Note:** If your `results` array contain less values than challenges added back in the UI, the "extra" UI challenges would automatically stay as "false". If you add more challenges in test file, the results would be ignored. Therefore, it is **important** that the `results.length` is same as the number of challenges you added in the challenges UI. |
| 198 | + |
| 199 | +- We then also add jQuery and chai for assisting with testing. Although it is not required as long as you can populate the `results` array properly. |
| 200 | + |
| 201 | +This completes your evaluation script for the lab. Your lab is now almost ready for users. |
| 202 | + |
| 203 | +## Setup Verified Solution (Recommended) |
| 204 | + |
| 205 | +<!--@include: ./../_components/LabVerifiedSolution.md--> |
0 commit comments