diff --git a/.gitignore b/.gitignore index 0bc8fb85..f124186a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,39 @@ *.pyc *.sw? *~ \#* .\#* /.coverage /.coverage.* .eggs/ resources/test/ __pycache__ version.txt swh.web.egg-info docs/build/ docs/uri-scheme.md docs/dev-info.md *.sqlite3 .vscode/ .directory node_modules/ swh/web/static/*.* swh/web/static/js/ swh/web/static/css/ swh/web/static/fonts/ swh/web/static/jssources/ swh/web/static/img/thirdParty/ .cache-loader/ build/ dist/ .hypothesis .cache .pytest_cache .tox/ debian/ package-lock.json yarn-error.log cypress/mochawesome/ .nyc_output/ cypress/coverage/ +cypress/fixtures/source*.json diff --git a/cypress.json b/cypress.json index 03cacb09..31613afc 100644 --- a/cypress.json +++ b/cypress.json @@ -1,18 +1,19 @@ { "baseUrl": "http://localhost:5004", "video": false, "viewportWidth": 1920, "viewportHeight": 1080, "defaultCommandTimeout": 10000, + "numTestsKeptInMemory": 500, "reporter": "cypress-multi-reporters", "reporterOptions": { "reporterEnabled": "mochawesome", "mochawesomeReporterOptions": { "reportDir": "cypress/mochawesome/results", "quiet": true, "overwrite": false, "html": false, "json": true } } } diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 00000000..da18d935 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/cypress/fixtures/source-file-extensions.json b/cypress/fixtures/source-file-extensions.json deleted file mode 100644 index f9aa1f4d..00000000 --- a/cypress/fixtures/source-file-extensions.json +++ /dev/null @@ -1 +0,0 @@ -["R","abnf","adb","adoc","ads","ahk","aj","applescript","as","au3","awk","bas","bat","bf","bnf","bsl","cal","capnp","cc","ceylon","clj","cls","cmake","coffee","cpp","cr","cs","css","d","dart","dcl","dfm","diff","do","dts","dust","ebnf","elm","ep","erb","erl","ex","exs","f90","feature","flix","fs","gcode","glsl","gml","gms","go","golo","gradle","groovy","gss","haml","hbs","hs","hsp","hx","hy","icl","ini","ino","java","jl","js","json","kt","lasso","lc","ldif","leaf","less","lisp","ll","ls","lsl","lua","m","md","mel","mk","ml","moon","nb","nim","nix","nsi","p","pas","pbi","pde","php","pl","pm","pony","pp","properties","proto","ps1","py","q","qml","rb","re","rib","rs","rsc","s","sas","scad","scala","sci","scm","scss","sh","sl","smali","sml","sqf","st","stan","styl","subunit","swift","tap","tcl","tex","thrift","ts","v","vala","vb","vbs","vhd","vim","wl","xml","xqy","yml","zep"] \ No newline at end of file diff --git a/cypress/fixtures/source-file-names.json b/cypress/fixtures/source-file-names.json deleted file mode 100644 index 5e1bee50..00000000 --- a/cypress/fixtures/source-file-names.json +++ /dev/null @@ -1 +0,0 @@ -[".htaccess","CMakeLists.txt","Dockerfile","Makefile","access.log","httpd.conf","nginx.conf","nginx.log","pf.conf","resolv.conf"] \ No newline at end of file diff --git a/cypress/integration/content-rendering.spec.js b/cypress/integration/content-rendering.spec.js new file mode 100644 index 00000000..1e05411f --- /dev/null +++ b/cypress/integration/content-rendering.spec.js @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2019 The Software Heritage developers + * See the AUTHORS file at the top-level directory of this distribution + * License: GNU Affero General Public License version 3, or any later version + * See top-level LICENSE file for more information + */ + +import {checkLanguageHighlighting} from '../utils'; + +describe('Code highlighting tests', function() { + + const extensions = require('../fixtures/source-file-extensions.json'); + + extensions.forEach(ext => { + it(`should highlight source files with extension ${ext}`, function() { + cy.request(this.Urls.tests_content_code_extension(ext)).then(response => { + let data = response.body; + cy.visit(`${this.Urls.browse_content(data.sha1)}?path=file.${ext}`); + checkLanguageHighlighting(data.language); + }); + }); + }); + + const filenames = require('../fixtures/source-file-names.json'); + + filenames.forEach(filename => { + it(`should highlight source files with filenames ${filename}`, function() { + cy.request(this.Urls.tests_content_code_filename(filename)).then(response => { + let data = response.body; + cy.visit(`${this.Urls.browse_content(data.sha1)}?path=${filename}`); + checkLanguageHighlighting(data.language); + }); + }); + }); + +}); + +describe('Image rendering tests', function() { + const imgExtensions = ['gif', 'jpeg', 'png', 'webp']; + + imgExtensions.forEach(ext => { + it(`should render image with extension ${ext}`, function() { + cy.request(this.Urls.tests_content_other_extension(ext)).then(response => { + let data = response.body; + cy.visit(`${this.Urls.browse_content(data.sha1)}?path=file.${ext}`); + cy.get('.swh-content img') + .then(img => { + assert.notEqual(img[0].width, 0); + assert.notEqual(img[0].height, 0); + }); + }); + }); + }); + +}); + +describe('PDF rendering test', function() { + + function sum(previousValue, currentValue) { + return previousValue + currentValue; + } + + it(`should render a PDF file`, function() { + cy.request(this.Urls.tests_content_other_extension('pdf')).then(response => { + let data = response.body; + cy.visit(`${this.Urls.browse_content(data.sha1)}?path=file.pdf`); + cy.get('.swh-content canvas') + .wait(2000) + .then(canvas => { + let width = canvas[0].width; + let height = canvas[0].height; + let context = canvas[0].getContext('2d'); + let imgData = context.getImageData(0, 0, width, height); + assert.notEqual(imgData.data.reduce(sum), 0); + }); + }); + }); + +}); + +describe('Jupyter notebook rendering test', function() { + + it(`should render a notebook file to HTML`, function() { + cy.request(this.Urls.tests_content_other_extension('ipynb')).then(response => { + let data = response.body; + cy.visit(`${this.Urls.browse_content(data.sha1)}?path=file.ipynb`); + cy.get('.nb-notebook') + .should('be.visible') + .and('not.be.empty'); + cy.get('.nb-cell.nb-markdown-cell') + .should('be.visible') + .and('not.be.empty'); + cy.get('.nb-cell.nb-code-cell') + .should('be.visible') + .and('not.be.empty'); + cy.get('.MathJax') + .should('be.visible') + .and('not.be.empty'); + }); + }); + +}); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 7866302a..eb208b83 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -1,10 +1,26 @@ /** * Copyright (C) 2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ +const axios = require('axios'); +const fs = require('fs'); + module.exports = (on, config) => { on('task', require('@cypress/code-coverage/task')); + // produce JSON files prior launching browser in order to dynamically generate tests + on('before:browser:launch', function(browser = {}, args) { + return new Promise((resolve) => { + let p1 = axios.get(`${config.baseUrl}/tests/data/content/code/extensions/`); + let p2 = axios.get(`${config.baseUrl}/tests/data/content/code/filenames/`); + Promise.all([p1, p2]) + .then(function(responses) { + fs.writeFileSync('cypress/fixtures/source-file-extensions.json', JSON.stringify(responses[0].data)); + fs.writeFileSync('cypress/fixtures/source-file-names.json', JSON.stringify(responses[1].data)); + resolve(args); + }); + }); + }); }; diff --git a/cypress/utils/index.js b/cypress/utils/index.js index dc71a4a1..34df946d 100644 --- a/cypress/utils/index.js +++ b/cypress/utils/index.js @@ -1,36 +1,47 @@ /** * Copyright (C) 2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import axios from 'axios'; export async function httpGetJson(url) { const response = await axios.get(url); return response.data; } /** * Converts string with Time information * to an object with Time information */ export function getTime(text) { const date = new Date(text); function pad(n) { return n < 10 ? '0' + n : n; } const time = { date: date.getUTCDate(), month: date.getUTCMonth(), monthName: date.toLocaleString('en', { month: 'long' }), year: date.getUTCFullYear(), hours: pad(date.getUTCHours()), minutes: pad(date.getUTCMinutes()) }; return time; } + +export function checkLanguageHighlighting(language) { + cy.get('code') + .should('be.visible') + .and('have.class', 'hljs') + .and('have.class', language) + .and('not.be.empty') + .find('table.hljs-ln') + .should('be.visible') + .and('not.be.empty'); +} diff --git a/swh/web/config.py b/swh/web/config.py index e6af73a4..5cecf2f4 100644 --- a/swh/web/config.py +++ b/swh/web/config.py @@ -1,154 +1,155 @@ # Copyright (C) 2017-2019 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information import os from swh.core import config from swh.indexer.storage import get_indexer_storage from swh.scheduler import get_scheduler from swh.storage import get_storage from swh.vault import get_vault from swh.web import settings SETTINGS_DIR = os.path.dirname(settings.__file__) DEFAULT_CONFIG = { 'allowed_hosts': ('list', []), 'storage': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://127.0.0.1:5002/', 'timeout': 10, }, }), 'indexer_storage': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://127.0.0.1:5007/', 'timeout': 1, } }), 'log_dir': ('string', '/tmp/swh/log'), 'debug': ('bool', False), 'serve_assets': ('bool', False), 'host': ('string', '127.0.0.1'), 'port': ('int', 5004), 'secret_key': ('string', 'development key'), # do not display code highlighting for content > 1MB 'content_display_max_size': ('int', 5 * 1024 * 1024), 'snapshot_content_max_size': ('int', 1000), 'throttling': ('dict', { 'cache_uri': None, # production: memcached as cache (127.0.0.1:11211) # development: in-memory cache so None 'scopes': { 'swh_api': { 'limiter_rate': { 'default': '120/h' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_vault_cooking': { 'limiter_rate': { 'default': '120/h', 'GET': '60/m' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_save_origin': { 'limiter_rate': { 'default': '120/h', 'POST': '10/h' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_api_origin_visit_latest': { 'limiter_rate': { 'default': '700/m' }, 'exempted_networks': ['127.0.0.0/8'], }, } }), 'vault': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://127.0.0.1:5005/', } }), 'scheduler': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://127.0.0.1:5008/' } }), 'development_db': ('string', os.path.join(SETTINGS_DIR, 'db.sqlite3')), 'production_db': ('string', '/var/lib/swh/web.sqlite3'), 'deposit': ('dict', { 'private_api_url': 'https://deposit.softwareheritage.org/1/private/', 'private_api_user': 'swhworker', 'private_api_password': '' }), - 'coverage_count_origins': ('bool', False) + 'coverage_count_origins': ('bool', False), + 'e2e_tests_mode': ('bool', False) } swhweb_config = {} def get_config(config_file='web/web'): """Read the configuration file `config_file`. If an environment variable SWH_CONFIG_FILENAME is defined, this takes precedence over the config_file parameter. In any case, update the app with parameters (secret_key, conf) and return the parsed configuration as a dict. If no configuration file is provided, return a default configuration. """ if not swhweb_config: config_filename = os.environ.get('SWH_CONFIG_FILENAME') if config_filename: config_file = config_filename cfg = config.load_named_config(config_file, DEFAULT_CONFIG) swhweb_config.update(cfg) config.prepare_folders(swhweb_config, 'log_dir') swhweb_config['storage'] = get_storage(**swhweb_config['storage']) swhweb_config['vault'] = get_vault(**swhweb_config['vault']) swhweb_config['indexer_storage'] = \ get_indexer_storage(**swhweb_config['indexer_storage']) swhweb_config['scheduler'] = get_scheduler( **swhweb_config['scheduler']) return swhweb_config def storage(): """Return the current application's storage. """ return get_config()['storage'] def vault(): """Return the current application's vault. """ return get_config()['vault'] def indexer_storage(): """Return the current application's indexer storage. """ return get_config()['indexer_storage'] def scheduler(): """Return the current application's scheduler. """ return get_config()['scheduler'] diff --git a/swh/web/settings/tests.py b/swh/web/settings/tests.py index 5e83891c..a3becee4 100644 --- a/swh/web/settings/tests.py +++ b/swh/web/settings/tests.py @@ -1,94 +1,95 @@ # Copyright (C) 2017-2019 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information """ Django tests settings for swh-web. """ import sys from swh.web.config import get_config scope1_limiter_rate = 3 scope1_limiter_rate_post = 1 scope2_limiter_rate = 5 scope2_limiter_rate_post = 2 scope3_limiter_rate = 1 scope3_limiter_rate_post = 1 save_origin_rate_post = 10 swh_web_config = get_config() swh_web_config.update({ 'debug': False, 'secret_key': 'test', 'throttling': { 'cache_uri': None, 'scopes': { 'swh_api': { 'limiter_rate': { 'default': '60/min' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_api_origin_visit_latest': { 'limiter_rate': { 'default': '6000/min' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_vault_cooking': { 'limiter_rate': { 'default': '120/h', 'GET': '60/m' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_save_origin': { 'limiter_rate': { 'default': '120/h', 'POST': '%s/h' % save_origin_rate_post, } }, 'scope1': { 'limiter_rate': { 'default': '%s/min' % scope1_limiter_rate, 'POST': '%s/min' % scope1_limiter_rate_post, } }, 'scope2': { 'limiter_rate': { 'default': '%s/min' % scope2_limiter_rate, 'POST': '%s/min' % scope2_limiter_rate_post } }, 'scope3': { 'limiter_rate': { 'default': '%s/min' % scope3_limiter_rate, 'POST': '%s/min' % scope3_limiter_rate_post }, 'exempted_networks': ['127.0.0.0/8'] } } } }) from .common import * # noqa from .common import ALLOWED_HOSTS, LOGGING # noqa # when not running unit tests, make the webapp fetch data from memory storages if 'pytest' not in sys.argv[0]: swh_web_config.update({ 'debug': True, + 'e2e_tests_mode': True }) from swh.web.tests.data import get_tests_data, override_storages # noqa test_data = get_tests_data() override_storages(test_data['storage'], test_data['idx_storage']) else: ALLOWED_HOSTS += ['testserver'] # Silent DEBUG output when running unit tests LOGGING['handlers']['console']['level'] = 'INFO' diff --git a/swh/web/tests/data.py b/swh/web/tests/data.py index 77a83794..876050e8 100644 --- a/swh/web/tests/data.py +++ b/swh/web/tests/data.py @@ -1,322 +1,469 @@ # Copyright (C) 2018-2019 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from copy import deepcopy import os import random + +from rest_framework.decorators import api_view +from rest_framework.response import Response + from swh.indexer.fossology_license import FossologyLicenseIndexer from swh.indexer.mimetype import MimetypeIndexer from swh.indexer.ctags import CtagsIndexer from swh.indexer.storage import get_indexer_storage +from swh.model.from_disk import Directory from swh.model.hashutil import hash_to_hex, hash_to_bytes, DEFAULT_ALGORITHMS from swh.model.identifiers import directory_identifier from swh.loader.git.from_disk import GitLoaderFromArchive from swh.storage.algos.dir_iterators import dir_iterator from swh.web import config from swh.web.browse.utils import ( get_mimetype_and_encoding_for_content, prepare_content_for_display ) from swh.web.common import service +from swh.web.common.highlightjs import get_hljs_language_from_filename # Module used to initialize data that will be provided as tests input # Configuration for git loader _TEST_LOADER_CONFIG = { 'storage': { 'cls': 'memory', 'args': {} }, 'send_contents': True, 'send_directories': True, 'send_revisions': True, 'send_releases': True, 'send_snapshot': True, 'content_size_limit': 100 * 1024 * 1024, 'content_packet_size': 10, 'content_packet_size_bytes': 100 * 1024 * 1024, 'directory_packet_size': 10, 'revision_packet_size': 10, 'release_packet_size': 10, 'save_data': False, } # Base content indexer configuration _TEST_INDEXER_BASE_CONFIG = { 'storage': { 'cls': 'memory', 'args': {}, }, 'objstorage': { 'cls': 'memory', 'args': {}, }, 'indexer_storage': { 'cls': 'memory', 'args': {}, } } def random_sha1(): return hash_to_hex(bytes(random.randint(0, 255) for _ in range(20))) def random_sha256(): return hash_to_hex(bytes(random.randint(0, 255) for _ in range(32))) def random_blake2s256(): return hash_to_hex(bytes(random.randint(0, 255) for _ in range(32))) def random_content(): return { 'sha1': random_sha1(), 'sha1_git': random_sha1(), 'sha256': random_sha256(), 'blake2s256': random_blake2s256(), } # MimetypeIndexer with custom configuration for tests class _MimetypeIndexer(MimetypeIndexer): def parse_config_file(self, *args, **kwargs): return { **_TEST_INDEXER_BASE_CONFIG, 'tools': { 'name': 'file', 'version': '1:5.30-1+deb9u1', 'configuration': { "type": "library", "debian-package": "python3-magic" } } } # FossologyLicenseIndexer with custom configuration for tests class _FossologyLicenseIndexer(FossologyLicenseIndexer): def parse_config_file(self, *args, **kwargs): return { **_TEST_INDEXER_BASE_CONFIG, 'workdir': '/tmp/swh/indexer.fossology.license', 'tools': { 'name': 'nomos', 'version': '3.1.0rc2-31-ga2cbb8c', 'configuration': { 'command_line': 'nomossa ', }, } } # CtagsIndexer with custom configuration for tests class _CtagsIndexer(CtagsIndexer): def parse_config_file(self, *args, **kwargs): return { **_TEST_INDEXER_BASE_CONFIG, 'workdir': '/tmp/swh/indexer.ctags', 'languages': {'c': 'c'}, 'tools': { 'name': 'universal-ctags', 'version': '~git7859817b', 'configuration': { 'command_line': '''ctags --fields=+lnz --sort=no --links=no ''' # noqa '''--output-format=json ''' }, } } # Lightweight git repositories that will be loaded to generate # input data for tests _TEST_ORIGINS = [ { 'type': 'git', 'url': 'https://github.com/wcoder/highlightjs-line-numbers.js', 'archives': ['highlightjs-line-numbers.js.zip', 'highlightjs-line-numbers.js_visit2.zip'], 'visit_date': ['Dec 1 2018, 01:00 UTC', 'Jan 20 2019, 15:00 UTC'] }, { 'type': 'git', 'url': 'https://github.com/memononen/libtess2', 'archives': ['libtess2.zip'], 'visit_date': ['May 25 2018, 01:00 UTC'] }, { 'type': 'git', 'url': 'repo_with_submodules', 'archives': ['repo_with_submodules.tgz'], 'visit_date': ['Jan 1 2019, 01:00 UTC'] } ] _contents = {} # Tests data initialization def _init_tests_data(): # Load git repositories from archives loader = GitLoaderFromArchive(config=_TEST_LOADER_CONFIG) # Get reference to the memory storage storage = loader.storage for origin in _TEST_ORIGINS: for i, archive in enumerate(origin['archives']): origin_repo_archive = \ os.path.join(os.path.dirname(__file__), 'resources/repos/%s' % archive) loader.load(origin['url'], origin_repo_archive, origin['visit_date'][i]) origin.update(storage.origin_get(origin)) # add an 'id' key if enabled contents = set() directories = set() revisions = set() releases = set() snapshots = set() persons = set() content_path = {} # Get all objects loaded into the test archive for origin in _TEST_ORIGINS: snp = storage.snapshot_get_latest(origin['url']) snapshots.add(hash_to_hex(snp['id'])) for branch_name, branch_data in snp['branches'].items(): if branch_data['target_type'] == 'revision': revisions.add(branch_data['target']) elif branch_data['target_type'] == 'release': release = next(storage.release_get([branch_data['target']])) revisions.add(release['target']) releases.add(hash_to_hex(branch_data['target'])) persons.add(release['author']['id']) for rev_log in storage.revision_shortlog(set(revisions)): rev_id = rev_log[0] revisions.add(rev_id) for rev in storage.revision_get(revisions): dir_id = rev['directory'] persons.add(rev['author']['id']) persons.add(rev['committer']['id']) directories.add(hash_to_hex(dir_id)) for entry in dir_iterator(storage, dir_id): content_path[entry['sha1']] = '/'.join( [hash_to_hex(dir_id), entry['path'].decode('utf-8')]) if entry['type'] == 'file': contents.add(entry['sha1']) elif entry['type'] == 'dir': directories.add(hash_to_hex(entry['target'])) # Get all checksums for each content contents_metadata = storage.content_get_metadata(contents) contents = [] for content_metadata in contents_metadata: contents.append({ algo: hash_to_hex(content_metadata[algo]) for algo in DEFAULT_ALGORITHMS }) path = content_path[content_metadata['sha1']] cnt = next(storage.content_get([content_metadata['sha1']])) mimetype, encoding = get_mimetype_and_encoding_for_content(cnt['data']) content_display_data = prepare_content_for_display( cnt['data'], mimetype, path) contents[-1]['path'] = path contents[-1]['mimetype'] = mimetype contents[-1]['encoding'] = encoding contents[-1]['hljs_language'] = content_display_data['language'] contents[-1]['data'] = content_display_data['content_data'] _contents[contents[-1]['sha1']] = contents[-1] # Create indexer storage instance that will be shared by indexers idx_storage = get_indexer_storage('memory', {}) # Add the empty directory to the test archive empty_dir_id = directory_identifier({'entries': []}) empty_dir_id_bin = hash_to_bytes(empty_dir_id) storage.directory_add([{'id': empty_dir_id_bin, 'entries': []}]) # Return tests data return { 'storage': storage, 'idx_storage': idx_storage, 'origins': _TEST_ORIGINS, 'contents': contents, 'directories': list(directories), 'persons': list(persons), 'releases': list(releases), 'revisions': list(map(hash_to_hex, revisions)), 'snapshots': list(snapshots), 'generated_checksums': set(), } def _init_indexers(tests_data): # Instantiate content indexers that will be used in tests # and force them to use the memory storages indexers = {} for idx_name, idx_class in (('mimetype_indexer', _MimetypeIndexer), ('license_indexer', _FossologyLicenseIndexer), ('ctags_indexer', _CtagsIndexer)): idx = idx_class() idx.storage = tests_data['storage'] idx.objstorage = tests_data['storage'].objstorage idx.idx_storage = tests_data['idx_storage'] idx.register_tools(idx.config['tools']) indexers[idx_name] = idx return indexers def get_content(content_sha1): return _contents.get(content_sha1) _tests_data = None _current_tests_data = None _indexer_loggers = {} def get_tests_data(reset=False): """ Initialize tests data and return them in a dict. """ global _tests_data, _current_tests_data if _tests_data is None: _tests_data = _init_tests_data() indexers = _init_indexers(_tests_data) for (name, idx) in indexers.items(): # pytest makes the loggers use a temporary file; and deepcopy # requires serializability. So we remove them, and add them # back after the copy. _indexer_loggers[name] = idx.log del idx.log _tests_data.update(indexers) if reset or _current_tests_data is None: _current_tests_data = deepcopy(_tests_data) for (name, logger) in _indexer_loggers.items(): _current_tests_data[name].log = logger return _current_tests_data def override_storages(storage, idx_storage): """ Helper function to replace the storages from which archive data are fetched. """ swh_config = config.get_config() swh_config.update({'storage': storage}) service.storage = storage swh_config.update({'indexer_storage': idx_storage}) service.idx_storage = idx_storage + + +# Implement some special endpoints used to provide input tests data +# when executing end to end tests with cypress + +_content_code_data_exts = {} +_content_code_data_filenames = {} +_content_other_data_exts = {} + + +def _init_content_tests_data(data_path, data_dict, ext_key): + """ + Helper function to read the content of a directory, store it + into a test archive and add some files metadata (sha1 and/or + expected programming language) in a dict. + + Args: + data_path (str): path to a directory relative to the tests + folder of swh-web + data_dict (dict): the dict that will store files metadata + ext_key (bool): whether to use file extensions or filenames + as dict keys + """ + test_contents_dir = os.path.join( + os.path.dirname(__file__), data_path).encode('utf-8') + directory = Directory.from_disk(path=test_contents_dir, data=True, + save_path=True) + objects = directory.collect() + for c in objects['content'].values(): + c['status'] = 'visible' + sha1 = hash_to_hex(c['sha1']) + if ext_key: + key = c['path'].decode('utf-8').split('.')[-1] + filename = 'test.' + key + else: + filename = c['path'].decode('utf-8').split('/')[-1] + key = filename + language = get_hljs_language_from_filename(filename) + data_dict[key] = {'sha1': sha1, + 'language': language} + storage = get_tests_data()['storage'] + storage.content_add(objects['content'].values()) + + +def _init_content_code_data_exts(): + """ + Fill a global dictionary which maps source file extension to + a code content example. + """ + global _content_code_data_exts + _init_content_tests_data('resources/contents/code/extensions', + _content_code_data_exts, True) + + +def _init_content_other_data_exts(): + """ + Fill a global dictionary which maps a file extension to + a content example. + """ + global _content_other_data_exts + _init_content_tests_data('resources/contents/other/extensions', + _content_other_data_exts, True) + + +def _init_content_code_data_filenames(): + """ + Fill a global dictionary which maps a filename to + a content example. + """ + global _content_code_data_filenames + _init_content_tests_data('resources/contents/code/filenames', + _content_code_data_filenames, False) + + +if config.get_config()['e2e_tests_mode']: + _init_content_code_data_exts() + _init_content_other_data_exts() + _init_content_code_data_filenames() + + +@api_view(['GET']) +def get_content_code_data_all_exts(request): + """ + Endpoint implementation returning a list of all source file + extensions to test for highlighting using cypress. + """ + return Response(sorted(_content_code_data_exts.keys()), + status=200, content_type='application/json') + + +@api_view(['GET']) +def get_content_code_data_by_ext(request, ext): + """ + Endpoint implementation returning metadata of a code content example + based on the source file extension. + """ + data = None + status = 404 + if ext in _content_code_data_exts: + data = _content_code_data_exts[ext] + status = 200 + return Response(data, status=status, content_type='application/json') + + +@api_view(['GET']) +def get_content_other_data_by_ext(request, ext): + """ + Endpoint implementation returning metadata of a content example + based on the file extension. + """ + _init_content_other_data_exts() + data = None + status = 404 + if ext in _content_other_data_exts: + data = _content_other_data_exts[ext] + status = 200 + return Response(data, status=status, content_type='application/json') + + +@api_view(['GET']) +def get_content_code_data_all_filenames(request): + """ + Endpoint implementation returning a list of all source filenames + to test for highlighting using cypress. + """ + return Response(sorted(_content_code_data_filenames.keys()), + status=200, content_type='application/json') + + +@api_view(['GET']) +def get_content_code_data_by_filename(request, filename): + """ + Endpoint implementation returning metadata of a code content example + based on the source filename. + """ + data = None + status = 404 + if filename in _content_code_data_filenames: + data = _content_code_data_filenames[filename] + status = 200 + return Response(data, status=status, content_type='application/json') diff --git a/swh/web/tests/resources/contents/code/LICENSE b/swh/web/tests/resources/contents/code/LICENSE new file mode 100644 index 00000000..cf0f4de9 --- /dev/null +++ b/swh/web/tests/resources/contents/code/LICENSE @@ -0,0 +1,28 @@ +All the test source files are taken from the highlight.js demo website +(see https://highlightjs.org/static/demo/ and + https://github.com/highlightjs/highlight.js/tree/master/test/detect) + +Copyright (c) 2006, Ivan Sagalaev +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of highlight.js nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/swh/web/tests/resources/contents/code/extensions/test.R b/swh/web/tests/resources/contents/code/extensions/test.R new file mode 100644 index 00000000..d9e5175d --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.R @@ -0,0 +1,68 @@ +library(ggplot2) + +centre <- function(x, type, ...) { + switch(type, + mean = mean(x), + median = median(x), + trimmed = mean(x, trim = .1)) +} + +myVar1 +myVar.2 +data$x +foo "bar" baz +# test "test" +"test # test" + +(123) (1) (10) (0.1) (.2) (1e-7) +(1.2e+7) (2e) (3e+10) (0x0) (0xa) +(0xabcdef1234567890) (123L) (1L) +(0x10L) (10000000L) (1e6L) (1.1L) +(1e-3L) (4123.381E-10i) +(3.) (3.E10) # BUG: .E10 should be part of number + +# Numbers in some different contexts +1L +0x40 +.234 +3. +1L + 30 +plot(cars, xlim=20) +plot(cars, xlim=0x20) +foo<-30 +my.data.3 <- read() # not a number +c(1,2,3) +1%%2 + +"this is a quote that spans +multiple lines +\" + +is this still a quote? it should be. +# even still! + +" # now we're done. + +'same for +single quotes #' + +# keywords +NULL, NA, TRUE, FALSE, Inf, NaN, NA_integer_, +NA_real_, NA_character_, NA_complex_, function, +while, repeat, for, if, in, else, next, break, +..., ..1, ..2 + +# not keywords +the quick brown fox jumped over the lazy dogs +null na true false inf nan na_integer_ na_real_ +na_character_ na_complex_ Function While Repeat +For If In Else Next Break .. .... "NULL" `NULL` 'NULL' + +# operators ++, -, *, /, %%, ^, >, >=, <, <=, ==, !=, !, &, |, ~, +->, <-, <<-, $, :, :: + +# infix operator +foo %union% bar +%"test"% +`"test"` \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.abnf b/swh/web/tests/resources/contents/code/extensions/test.abnf new file mode 100644 index 00000000..4fba0e39 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.abnf @@ -0,0 +1,22 @@ +; line comment + +ruleset = [optional] *(group1 / group2 / SP) CRLF ; trailing comment + +group1 = alt1 +group1 =/ alt2 + +alt1 = %x41-4D / %d78-90 + +alt2 = %b00100001 + +group2 = *1DIGIT / 2*HEXDIG / 3*4OCTET + +optional = hex-codes + / literal + / sensitive + / insensitive + +hex-codes = %x68.65.6C.6C.6F +literal = "string literal" +sensitive = %s"case-sensitive string" +insensitive = %i"case-insensitive string" diff --git a/swh/web/tests/resources/contents/code/extensions/test.adb b/swh/web/tests/resources/contents/code/extensions/test.adb new file mode 100644 index 00000000..09318ae0 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.adb @@ -0,0 +1,17 @@ +package body Sqlite.Simple is + + Foo : int := int'Size; + Bar : int := long'Size; + + Error_Message_C : chars_ptr := Sqlite_Errstr (Error); + Error_Message : String := Null_Ignore_Value (Error_Message_C); + begin + + Named : for Index in Foo..Bar loop + Put ("Hi[]{}"); + end loop Named; + + Foo := Bar; + end Message; + +end Sqlite.Simple; diff --git a/swh/web/tests/resources/contents/code/extensions/test.adoc b/swh/web/tests/resources/contents/code/extensions/test.adoc new file mode 100644 index 00000000..2af6ee6c --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.adoc @@ -0,0 +1,65 @@ +Hello, World! +============ +Author Name, + +you can write text http://example.com[with links], optionally +using an explicit link:http://example.com[link prefix]. + +* single quotes around a phrase place 'emphasis' +** alternatively, you can put underlines around a phrase to add _emphasis_ +* astericks around a phrase make the text *bold* +* pluses around a phrase make it +monospaced+ +* `smart' quotes using a leading backtick and trailing single quote +** use two of each for double ``smart'' quotes + +- escape characters are supported +- you can escape a quote inside emphasized text like 'here\'s johnny!' + +term:: definition + another term:: another definition + +// this is just a comment + +Let's make a break. + +''' + +//// +we'll be right with you + +after this brief interruption. +//// + +== We're back! + +Want to see a image::images/tiger.png[Tiger]? + +.Nested highlighting +++++ + +++++ + +____ +asciidoc is so powerful. +____ + +another quote: + +[quote, Sir Arthur Conan Doyle, The Adventures of Sherlock Holmes] +____ +When you have eliminated all which is impossible, then whatever remains, however improbable, must be the truth. +____ + +Getting Literal +--------------- + + want to get literal? prefix a line with a space. + +.... +I'll join that party, too. +.... + +. one thing (yeah!) +. two thing `i can write code`, and `more` wipee! + +NOTE: AsciiDoc is quite cool, you should try it. \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.ahk b/swh/web/tests/resources/contents/code/extensions/test.ahk new file mode 100644 index 00000000..113ffdc3 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.ahk @@ -0,0 +1,24 @@ +; hotkeys and hotstrings +#a::WinSet, AlwaysOnTop, Toggle, A +#Space:: + MsgBox, Percent sign (`%) need to be escaped. + Run "C:\Program Files\some\program.exe" + Gosub, label1 +return +::btw::by the way +; volume +#Numpad8::Send {Volume_Up} +#Numpad5::Send {Volume_Mute} +#Numpad2::Send {Volume_Down} + +label1: + if (Clipboard = "") + { + MsgBox, , Clipboard, Empty! + } + else + { + StringReplace, temp, Clipboard, old, new, All + MsgBox, , Clipboard, %temp% + } +return \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.aj b/swh/web/tests/resources/contents/code/extensions/test.aj new file mode 100644 index 00000000..4cc90bef --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.aj @@ -0,0 +1,23 @@ +package com.aspectj.syntax; +import org.aspectj.lang.annotation.AdviceName; + +privileged public aspect LoggingAspect percflowbelow(ajia.services.*){ + private pointcut getResult() : call(* *(..) throws SQLException) && args(Account, .., int); + @AdviceName("CheckValidEmail") + before (Customer hu) : getResult(hu){ + System.out.println("Your mail address is valid!"); + } + Object around() throws InsufficientBalanceException: getResult() && call(Customer.new(String,String,int,int,int)){ + return proceed(); + } + public Cache getCache() { + return this.cache; + } + pointcut beanPropertyChange(BeanSupport bean, Object newValue): execution(void BeanSupport+.set*(*)) && args(newValue) && this(bean); + declare parents: banking.entities.* implements BeanSupport; + declare warning : call(void TestSoftening.perform()): "Please ensure you are not calling this from an AWT thread"; + private String Identifiable.id; + public void Identifiable.setId(String id) { + this.id = id; + } +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.applescript b/swh/web/tests/resources/contents/code/extensions/test.applescript new file mode 100644 index 00000000..c43e88ec --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.applescript @@ -0,0 +1,14 @@ +repeat 5 times + if foo is greater than bar then + display dialog "Hello there" + else + beep + end if +end repeat + +(* comment (*nested comment*) *) +on do_something(s, y) + return {s + pi, y mod 4} +end do_something + +do shell script "/bin/echo 'hello'" \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.as b/swh/web/tests/resources/contents/code/extensions/test.as new file mode 100644 index 00000000..b416de8e --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.as @@ -0,0 +1,24 @@ +package org.example.dummy { + import org.dummy.*; + + /*define package inline interface*/ + public interface IFooBarzable { + public function foo(... pairs):Array; + } + + public class FooBar implements IFooBarzable { + static private var cnt:uint = 0; + private var bar:String; + + //constructor + public function TestBar(bar:String):void { + bar = bar; + ++cnt; + } + + public function foo(... pairs):Array { + pairs.push(bar); + return pairs; + } + } +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.au3 b/swh/web/tests/resources/contents/code/extensions/test.au3 new file mode 100644 index 00000000..7946a548 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.au3 @@ -0,0 +1,16 @@ +#NoTrayIcon +#AutoIt3Wrapper_Run_Tidy=Y +#include + +_Singleton(@ScriptName) ; Allow only one instance +example(0, 10) + +Func example($min, $max) + For $i = $min To $max + If Mod($i, 2) == 0 Then + MsgBox(64, "Message", $i & ' is even number!') + Else + MsgBox(64, "Message", $i & ' is odd number!') + EndIf + Next +EndFunc ;==>example \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.awk b/swh/web/tests/resources/contents/code/extensions/test.awk new file mode 100644 index 00000000..8909d618 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.awk @@ -0,0 +1,16 @@ +BEGIN { + POPService = "/inet/tcp/0/emailhost/pop3" + RS = ORS = "\r\n" + print "user name" |& POPService + POPService |& getline + print "pass password" |& POPService + POPService |& getline + print "retr 1" |& POPService + POPService |& getline + if ($1 != "+OK") exit + print "quit" |& POPService + RS = "\r\n\\.\r\n" + POPService |& getline + print $0 + close(POPService) +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.bas b/swh/web/tests/resources/contents/code/extensions/test.bas new file mode 100644 index 00000000..04db6deb --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.bas @@ -0,0 +1,22 @@ +10 CLS +20 FOR I = 0 TO 15 +22 FOR J = 0 TO 7 +30 COLOR I,J +40 PRINT " ** "; +45 NEXT J +46 COLOR I,0 +47 GOSUB 100 +48 PRINT +50 NEXT I +60 COLOR 15,0 +99 END +100 FOR T = 65 TO 90 +101 PRINT CHR$(T); +102 NEXT T +103 RETURN +200 REM Data types test +201 TOTAL# = 3.30# 'Double precision variable +202 BALANCE! = 3! 'Single precision variable +203 B2! = 12e5 '120000 +204 ITEMS% = 10 'Integer variable +205 HEXTEST = &H12DB 'Hex value diff --git a/swh/web/tests/resources/contents/code/extensions/test.bat b/swh/web/tests/resources/contents/code/extensions/test.bat new file mode 100644 index 00000000..d911fed3 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.bat @@ -0,0 +1,24 @@ +cd \ +copy a b +ping 192.168.0.1 +@rem ping 192.168.0.1 +net stop sharedaccess +del %tmp% /f /s /q +del %temp% /f /s /q +ipconfig /flushdns +taskkill /F /IM JAVA.EXE /T + +cd Photoshop/Adobe Photoshop CS3/AMT/ +if exist application.sif ( + ren application.sif _application.sif +) else ( + ren _application.sif application.sif +) + +taskkill /F /IM proquota.exe /T + +sfc /SCANNOW + +set path = test + +xcopy %1\*.* %2 \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.bf b/swh/web/tests/resources/contents/code/extensions/test.bf new file mode 100644 index 00000000..5156af02 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.bf @@ -0,0 +1,17 @@ +++++++++++ +[ 3*10 and 10*10 + ->+++>++++++++++<< +]>> +[ filling cells + ->++>>++>++>+>++>>++>++>++>++>++>++>++>++>++>++>++[> +]< ++++++++++<< +[ rough codes correction loop + ->>>+>+>+>+++>+>+>+>+>+>+>+>+>+>+>+>+>+>+[<]< +] +more accurate сodes correction +>>>++> +-->+++++++>------>++++++>++>+++++++++>++++++++++>++++++++>--->++++++++++>------>++++++> +++>+++++++++++>++++++++++++>------>+++ +rewind and output +[<]>[.>] diff --git a/swh/web/tests/resources/contents/code/extensions/test.bnf b/swh/web/tests/resources/contents/code/extensions/test.bnf new file mode 100644 index 00000000..2149c65d --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.bnf @@ -0,0 +1,8 @@ + ::= | + ::= "<" ">" "::=" + ::= " " | "" + ::= | "|" + ::= | + ::= | + ::= | "<" ">" + ::= '"' '"' | "'" "'" diff --git a/swh/web/tests/resources/contents/code/extensions/test.bsl b/swh/web/tests/resources/contents/code/extensions/test.bsl new file mode 100644 index 00000000..d6dc7aac --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.bsl @@ -0,0 +1,30 @@ +#ЗагрузитьИзФайла ext_module.txt // директива 7.7 +#Если Клиент ИЛИ НаКлиенте Тогда // инструкции препроцессора + &НаКлиентеНаСервереБезКонтекста // директивы компиляции + Функция ТолстыйКлиентОбычноеПриложение(Знач Параметр1 = Неопределено, // комментарий + Параметр2 = "", ПараметрN = 123.45, ПарамNN) Экспорт // еще комментарий + Попытка + Результат_Булевы_Значения = Новый Структура("П1, П2", Истина, Ложь, NULL, Неопределено); + Перейти ~МеткаGOTO; // комментарий + РезультатТаблицаДат = Новый ТаблицаЗначений; + РезультатТаблицаДат.Колонки.Добавить("Колонка1", + Новый ОписаниеТипов("Дата", , , + Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя)); + НС = РезультатТаблицаДат.Добавить(); НС["Колонка1"] = '20170101120000'); + Исключение + ОписаниеОшибки = ОписаниеОшибки(); // встроенная функция + Масс = Новый Массив; // встроенный тип + Для Каждого Значение Из Масс Цикл + Сообщить(Значение + Символы.ПС + " + |продолжение строки"); // продолжение многострочной строки + Продолжить; Прервать; + КонецЦикла; + СправочникСсылка = Справочники.Языки.НайтиПоНаименованию("ru"); // встроенные типы + СправочникОбъект = СправочникСсылка.ПолучитьОбъект(); + ПеречислениеСсылка = Перечисления.ВидыМодификацииДанных.Изменен; + ВызватьИсключение ОписаниеОшибки; + КонецПопытки; + ~МеткаGOTO: // еще комментарий + ВД = ВидДвиженияБухгалтерии.Дебет; + КонецФункции // ТолстыйКлиентОбычноеПриложение() +#КонецЕсли \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.cal b/swh/web/tests/resources/contents/code/extensions/test.cal new file mode 100644 index 00000000..5eec86ca --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.cal @@ -0,0 +1,33 @@ +OBJECT Codeunit 11 Gen. Jnl.-Check Line +{ + OBJECT-PROPERTIES + { + Date=09-09-14; + Time=12:00:00; + Version List=NAVW18.00; + } + PROPERTIES + { + TableNo=81; + Permissions=TableData 252=rimd; + OnRun=BEGIN + GLSetup.GET; + RunCheck(Rec); + END; + + } + CODE + { + VAR + Text000@1000 : TextConst 'ENU=can only be a closing date for G/L entries'; + Text001@1001 : TextConst 'ENU=is not within your range of allowed posting dates'; + + PROCEDURE ErrorIfPositiveAmt@2(GenJnlLine@1000 : Record 81); + BEGIN + IF GenJnlLine.Amount > 0 THEN + GenJnlLine.FIELDERROR(Amount,Text008); + END; + + LOCAL PROCEDURE CheckGenJnlLineDocType@7(GenJnlLine@1001 : Record 81); + } +} diff --git a/swh/web/tests/resources/contents/code/extensions/test.capnp b/swh/web/tests/resources/contents/code/extensions/test.capnp new file mode 100644 index 00000000..f57fbea0 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.capnp @@ -0,0 +1,55 @@ +@0xdbb9ad1f14bf0b36; # unique file ID, generated by `capnp id` + +struct Person { + name @0 :Text; + birthdate @3 :Date; + + email @1 :Text; + phones @2 :List(PhoneNumber); + + struct PhoneNumber { + number @0 :Text; + type @1 :Type; + + enum Type { + mobile @0; + home @1; + work @2; + } + } +} + +struct Date { + year @0 :Int16; + month @1 :UInt8; + day @2 :UInt8; + flags @3 :List(Bool) = [ true, false, false, true ]; +} + +interface Node { + isDirectory @0 () -> (result :Bool); +} + +interface Directory extends(Node) { + list @0 () -> (list: List(Entry)); + struct Entry { + name @0 :Text; + node @1 :Node; + } + + create @1 (name :Text) -> (file :File); + mkdir @2 (name :Text) -> (directory :Directory) + open @3 (name :Text) -> (node :Node); + delete @4 (name :Text); + link @5 (name :Text, node :Node); +} + +interface File extends(Node) { + size @0 () -> (size: UInt64); + read @1 (startAt :UInt64 = 0, amount :UInt64 = 0xffffffffffffffff) + -> (data: Data); + # Default params = read entire file. + + write @2 (startAt :UInt64, data :Data); + truncate @3 (size :UInt64); +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.ceylon b/swh/web/tests/resources/contents/code/extensions/test.ceylon new file mode 100644 index 00000000..814079f7 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.ceylon @@ -0,0 +1,8 @@ +shared void run() { + print("Hello, `` process.arguments.first else "World" ``!"); +} +class Counter(count=0) { + variable Integer count; + shared Integer currentValue => count; + shared void increment() => count++; +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.clj b/swh/web/tests/resources/contents/code/extensions/test.clj new file mode 100644 index 00000000..55768f37 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.clj @@ -0,0 +1,11 @@ +(def ^:dynamic chunk-size 17) + +(defn next-chunk [rdr] + (let [buf (char-array chunk-size) + s (.read rdr buf)] + (when (pos? s) + (java.nio.CharBuffer/wrap buf 0 s)))) + +(defn chunk-seq [rdr] + (when-let [chunk (next-chunk rdr)] + (cons chunk (lazy-seq (chunk-seq rdr))))) \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.cls b/swh/web/tests/resources/contents/code/extensions/test.cls new file mode 100644 index 00000000..52924f13 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.cls @@ -0,0 +1,21 @@ +#dim test as %Library.Integer +SET test = 123.099 +set ^global = %request.Content +Write "Current date """, $ztimestamp, """, result: ", test + ^global = 125.099 +do ##class(Cinema.Utils).AddShow("test") // class method call +do ##super() ; another one-line comment +d:(^global = 2) ..thisClassMethod(1, 2, "test") +/* + * Sub-languages support: + */ +&sql( SELECT * FROM Cinema.Film WHERE Length > 2 ) +&js +&html< + + +Test +> + +quit $$$OK diff --git a/swh/web/tests/resources/contents/code/extensions/test.cmake b/swh/web/tests/resources/contents/code/extensions/test.cmake new file mode 100644 index 00000000..2bbea38c --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.cmake @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.8) +project(cmake_example) + +# Show message on Linux platform +if (${CMAKE_SYSTEM_NAME} MATCHES Linux) + message("Good choice, bro!") +endif() + +# Tell CMake to run moc when necessary: +set(CMAKE_AUTOMOC ON) +# As moc files are generated in the binary dir, +# tell CMake to always look for includes there: +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Widgets finds its own dependencies. +find_package(Qt5Widgets REQUIRED) + +add_executable(myproject main.cpp mainwindow.cpp) +qt5_use_modules(myproject Widgets) diff --git a/swh/web/tests/resources/contents/code/extensions/test.coffee b/swh/web/tests/resources/contents/code/extensions/test.coffee new file mode 100644 index 00000000..e4eeb954 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.coffee @@ -0,0 +1,13 @@ +grade = (student, period=(if b? then 7 else 6)) -> + if student.excellentWork + "A+" + else if student.okayStuff + if student.triedHard then "B" else "B-" + else + "C" + +class Animal extends Being + constructor: (@name) -> + + move: (meters) -> + alert @name + " moved #{meters}m." \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.cpp b/swh/web/tests/resources/contents/code/extensions/test.cpp new file mode 100644 index 00000000..6c4ef13c --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +using namespace std; + +int main(int argc, char *argv[]) { + + /* An annoying "Hello World" example */ + for (auto i = 0; i < 0xFFFF; i++) + cout << "Hello, World!" << endl; + + char c = '\n'; + unordered_map> m; + m["key"] = "\\\\"; // this is an error + + return -2e3 + 12l; +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.cr b/swh/web/tests/resources/contents/code/extensions/test.cr new file mode 100644 index 00000000..e55ddaba --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.cr @@ -0,0 +1,29 @@ +class Person + def initialize(@name : String) + end + + def greet + puts "Hi, I'm #{@name}" + end +end + +class Employee < Person +end + +employee = Employee.new "John" +employee.greet # => "Hi, I'm John" +employee.is_a?(Person) # => true + +@[Link("m")] +lib C + # In C: double cos(double x) + fun cos(value : Float64) : Float64 +end + +C.cos(1.5_f64) # => 0.0707372 + +s = uninitialized String +s = <<-'STR' +\hello\world +\hello\world +STR \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.cs b/swh/web/tests/resources/contents/code/extensions/test.cs new file mode 100644 index 00000000..b47e781e --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.cs @@ -0,0 +1,16 @@ +using System.IO.Compression; + +#pragma warning disable 414, 3021 + +namespace MyApplication +{ + [Obsolete("...")] + class Program : IInterface + { + public static List JustDoIt(int count) + { + Console.WriteLine($"Hello {Name}!"); + return new List(new int[] { 1, 2, 3 }) + } + } +} diff --git a/swh/web/tests/resources/contents/code/extensions/test.css b/swh/web/tests/resources/contents/code/extensions/test.css new file mode 100644 index 00000000..ede96c37 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.css @@ -0,0 +1,15 @@ +@font-face { + font-family: Chunkfive; src: url('Chunkfive.otf'); +} + +body, .usertext { + color: #F0F0F0; background: #600; + font-family: Chunkfive, sans; +} + +@import url(print.css); +@media print { + a[href^=http]::after { + content: attr(href) + } +} diff --git a/swh/web/tests/resources/contents/code/extensions/test.d b/swh/web/tests/resources/contents/code/extensions/test.d new file mode 100644 index 00000000..ee9ae84e --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.d @@ -0,0 +1,44 @@ +#!/usr/bin/rdmd +// Computes average line length for standard input. +import std.stdio; + +/+ + this is a /+ nesting +/ comment ++/ + +enum COMPILED_ON = __TIMESTAMP__; // special token + +enum character = '©'; +enum copy_valid = '©'; +enum backslash_escaped = '\\'; + +// string literals +enum str = `hello "world"!`; +enum multiline = r"lorem +ipsum +dolor"; // wysiwyg string, no escapes here allowed +enum multiline2 = "sit +amet +\"adipiscing\" +elit."; +enum hex = x"66 6f 6f"; // same as "foo" + +#line 5 + +// float literals +enum f = [3.14f, .1, 1., 1e100, 0xc0de.01p+100]; + +static if (something == true) { + import std.algorithm; +} + +void main() pure nothrow @safe { + ulong lines = 0; + double sumLength = 0; + foreach (line; stdin.byLine()) { + ++lines; + sumLength += line.length; + } + writeln("Average line length: ", + lines ? sumLength / lines : 0); +} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.dart b/swh/web/tests/resources/contents/code/extensions/test.dart new file mode 100644 index 00000000..a49b8e05 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.dart @@ -0,0 +1,37 @@ +library app; +import 'dart:html'; + +part 'app2.dart'; + +/** + * Class description and [link](http://dartlang.org/). + */ +@Awesome('it works!') +class SomeClass extends BaseClass implements Comparable { + factory SomeClass(num param); + SomeClass._internal(int q) : super() { + assert(q != 1); + double z = 0.0; + } + + /// **Sum** function + int sum(int a, int b) => a + b; + + ElementList els() => querySelectorAll('.dart'); +} + +String str = ' (${'parameter' + 'zxc'})'; +String str = " (${true ? 2 + 2 / 2 : null})"; +String str = " ($variable)"; +String str = r'\nraw\'; +String str = r"\nraw\"; +var str = ''' +Something ${2+3} +'''; +var str = r""" +Something ${2+3} +"""; + +checkVersion() async { + var version = await lookUpVersion(); +} diff --git a/swh/web/tests/resources/contents/code/extensions/test.dcl b/swh/web/tests/resources/contents/code/extensions/test.dcl new file mode 100644 index 00000000..293b37ac --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.dcl @@ -0,0 +1,30 @@ +module fsieve + +import StdClass; // RWS +import StdInt, StdReal + +NrOfPrimes :== 3000 + +primes :: [Int] +primes = pr where pr = [5 : sieve 7 4 pr] + +sieve :: Int !Int [Int] -> [Int] +sieve g i prs +| isPrime prs g (toInt (sqrt (toReal g))) = [g : sieve` g i prs] +| otherwise = sieve (g + i) (6 - i) prs + +sieve` :: Int Int [Int] -> [Int] +sieve` g i prs = sieve (g + i) (6 - i) prs + +isPrime :: [Int] !Int Int -> Bool +isPrime [f:r] pr bd +| f>bd = True +| pr rem f==0 = False +| otherwise = isPrime r pr bd + +select :: [x] Int -> x +select [f:r] 1 = f +select [f:r] n = select r (n - 1) + +Start :: Int +Start = select [2, 3 : primes] NrOfPrimes \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.dfm b/swh/web/tests/resources/contents/code/extensions/test.dfm new file mode 100644 index 00000000..04028560 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.dfm @@ -0,0 +1,30 @@ +TList = Class(TObject) +Private + Some: String; +Public + Procedure Inside; // Suxx +End;{TList} + +Procedure CopyFile(InFileName, var OutFileName: String); +Const + BufSize = 4096; (* Huh? *) +Var + InFile, OutFile: TStream; + Buffer: Array[1..BufSize] Of Byte; + ReadBufSize: Integer; +Begin + InFile := Nil; + OutFile := Nil; + Try + InFile := TFileStream.Create(InFileName, fmOpenRead); + OutFile := TFileStream.Create(OutFileName, fmCreate); + Repeat + ReadBufSize := InFile.Read(Buffer, BufSize); + OutFile.Write(Buffer, ReadBufSize); + Until ReadBufSize<>BufSize; + Log('File ''' + InFileName + ''' copied'#13#10); + Finally + InFile.Free; + OutFile.Free; + End;{Try} +End;{CopyFile} \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.diff b/swh/web/tests/resources/contents/code/extensions/test.diff new file mode 100644 index 00000000..4f4c04fe --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.diff @@ -0,0 +1,30 @@ +Index: languages/ini.js +=================================================================== +--- languages/ini.js (revision 199) ++++ languages/ini.js (revision 200) +@@ -1,8 +1,7 @@ + hljs.LANGUAGES.ini = + { + case_insensitive: true, +- defaultMode: +- { ++ defaultMode: { + contains: ['comment', 'title', 'setting'], + illegal: '[^\\s]' + }, + +*** /path/to/original timestamp +--- /path/to/new timestamp +*************** +*** 1,3 **** +--- 1,9 ---- ++ This is an important ++ notice! It should ++ therefore be located at ++ the beginning of this ++ document! + +! compress the size of the +! changes. + + It is important to spell diff --git a/swh/web/tests/resources/contents/code/extensions/test.do b/swh/web/tests/resources/contents/code/extensions/test.do new file mode 100644 index 00000000..63959e90 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.do @@ -0,0 +1,40 @@ +program define gr_log +version 6.0 + +local or = `2' +local xunits = `3' +local b1 = ln(`or') + +* make summary of logistic data from equation +set obs `xunits' +generate pgty = 1 - 1/(1 + exp(score)) +/** + * Comment 1 +*/ +reg y x +* Comment 2 +reg y2 x //comment 3 +This is a `loc' $glob ${glob2} +This is a `"string " "' "string`1'two${hi}" bad `"string " "' good `"string " "' + +//Limit to just the project ados +cap adopath - SITE +cap adopath - PLUS +/*cap adopath - PERSONAL +cap adopath - OLDPLACE*/ +adopath ++ "${dir_base}/code/ado/" +A string `"Wow"'. `""one" "two""' +A `local' em`b'ed +a global ${dir_base} $dir_base em${b}ed + +forval i=1/4{ + if `i'==2{ + cap reg y x1, robust + local x = ln(4) + local x =ln(4) + local ln = ln + } +} + +* add mlibs in the new adopath to the index +mata: mata mlib index \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.dts b/swh/web/tests/resources/contents/code/extensions/test.dts new file mode 100644 index 00000000..b65681ed --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "allwinner,sun4i-a10", "allwinner,sun7i-a20", "allwinner,sun8i-h3", "allwinner,sun50i-a64", "allwinner,sun50i-h5"; + + fragment@0 { + target = <&pio>; + __overlay__ { + apds9960_pin_irq: apds9960_pin_irq { + pins = "PA10"; + function = "irq"; + bias-pull-up; + }; + }; + }; + + fragment@1 { + target = <&i2c0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + apds9960@39 { + compatible = "avago,apds9960"; + reg = <0x39>; + pinctrl-names = "default"; + pinctrl-0 = <&apds9960_pin_irq>; + interrupt-parent = <&pio>; + interrupts = <0 10 2>; /* PA10 IRQ_TYPE_EDGE_FALLING */ + status = "okay"; + }; + }; + }; +}; \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.dust b/swh/web/tests/resources/contents/code/extensions/test.dust new file mode 100644 index 00000000..11bc7eb0 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.dust @@ -0,0 +1,7 @@ +

Hours

+ +
    + {#users} +
  • {firstName}
  • {~n} + {/users} +
diff --git a/swh/web/tests/resources/contents/code/extensions/test.ebnf b/swh/web/tests/resources/contents/code/extensions/test.ebnf new file mode 100644 index 00000000..a51dc648 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.ebnf @@ -0,0 +1,12 @@ +(* line comment *) + +rule = [optional] , symbol , { letters } , ( digit | symbol ) ; + +optional = ? something unnecessary ? ; (* trailing comment *) + +symbol = '!' | '@' | '#' | '$' | '%' | '&' | '*' ; +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" + | "H" | "I" | "J" | "K" | "L" | "M" | "N" + | "O" | "P" | "Q" | "R" | "S" | "T" | "U" + | "V" | "W" | "X" | "Y" | "Z" ; \ No newline at end of file diff --git a/swh/web/tests/resources/contents/code/extensions/test.elm b/swh/web/tests/resources/contents/code/extensions/test.elm new file mode 100644 index 00000000..f2380e5a --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.elm @@ -0,0 +1,18 @@ +import Html exposing (div, button, text) +import Html.App exposing (beginnerProgram) +import Html.Events exposing (onClick) + +type Msg + = Increment + +main = + beginnerProgram + { model = 0, view = view + , update = \Increment model -> model + 1 } + +view model = + div [] [ div [] [ text (toString model) ] + , button [ onClick Increment ] [ text "+" ] ] + +chars = + String.cons 'C' <| String.cons 'h' <| "ars" diff --git a/swh/web/tests/resources/contents/code/extensions/test.ep b/swh/web/tests/resources/contents/code/extensions/test.ep new file mode 100644 index 00000000..2cd9426f --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.ep @@ -0,0 +1,20 @@ +%layout 'bootstrap'; +% title "Import your subs"; +%= form_for '/settings/import' => (method => 'post', enctype => 'multipart/form-data') => begin + %= file_field 'opmlfile' => multiple => 'true' + %= submit_button 'Import', 'class' => 'btn' +% end +
+% if ($subs) { +
+% for my $item (@$subs) { +% my ($dir, $align) = ($item->{rtl}) ? ('rtl', 'right') : ('ltr', 'left'); +
rss +<%== $item->{title} %> +
+
Categories +%= join q{, }, sort @{ $item->{categories} || [] }; +
+
+% } +
diff --git a/swh/web/tests/resources/contents/code/extensions/test.erb b/swh/web/tests/resources/contents/code/extensions/test.erb new file mode 100644 index 00000000..f7ea6203 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.erb @@ -0,0 +1,10 @@ +<%# this is a comment %> + +<% @posts.each do |post| %> +

<%= link_to post.title, post %>

+<% end %> + +<%- available_things = things.select(&:available?) -%> +<%%- x = 1 + 2 -%%> +<%% value = 'real string #{@value}' %%> +<%%= available_things.inspect %%> diff --git a/swh/web/tests/resources/contents/code/extensions/test.erl b/swh/web/tests/resources/contents/code/extensions/test.erl new file mode 100644 index 00000000..528e0644 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.erl @@ -0,0 +1,60 @@ +-module(ssh_cli). + +-behaviour(ssh_channel). + +-include("ssh.hrl"). +%% backwards compatibility +-export([listen/1, listen/2, listen/3, listen/4, stop/1]). + +if L =/= [] -> % If L is not empty + sum(L) / count(L); +true -> + error +end. + +%% state +-record(state, { + cm, + channel + }). + +-spec foo(integer()) -> integer(). +foo(X) -> 1 + X. + +test(Foo)->Foo. + +init([Shell, Exec]) -> + {ok, #state{shell = Shell, exec = Exec}}; +init([Shell]) -> + false = not true, + io:format("Hello, \"~p!~n", [atom_to_list('World')]), + {ok, #state{shell = Shell}}. + +concat([Single]) -> Single; +concat(RList) -> + EpsilonFree = lists:filter( + fun (Element) -> + case Element of + epsilon -> false; + _ -> true + end + end, + RList), + case EpsilonFree of + [Single] -> Single; + Other -> {concat, Other} + end. + +union_dot_union({union, _}=U1, {union, _}=U2) -> + union(lists:flatten( + lists:map( + fun (X1) -> + lists:map( + fun (X2) -> + concat([X1, X2]) + end, + union_to_list(U2) + ) + end, + union_to_list(U1) + ))). diff --git a/swh/web/tests/resources/contents/code/extensions/test.ex b/swh/web/tests/resources/contents/code/extensions/test.ex new file mode 100644 index 00000000..3bd83e83 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.ex @@ -0,0 +1,72 @@ +defmodule Display do + use GenServer + + alias IO.ANSI + alias Display.{ProgressBar, Intro, Failure, Notifications} + + def start_link do + GenServer.start_link(__MODULE__, %{clear_screen: true}, name: __MODULE__) + end + + def init(args) do + {:ok, args} + end + + def disable_clear do + GenServer.cast(__MODULE__, :disable_clear) + end + + def handle_cast(:disable_clear, state) do + {:noreply, %{state | clear_screen: false}} + end + + def handle_cast(:clear_screen, state = %{clear_screen: true}) do + IO.puts(ANSI.clear()) + IO.puts(ANSI.home()) + + {:noreply, state} + end + + def handle_cast(:clear_screen, state) do + {:noreply, state} + end + + def invalid_koan(koan, modules) do + Notifications.invalid_koan(koan, modules) + |> IO.puts() + end + + def show_failure(failure, module, name) do + format(failure, module, name) + |> IO.puts() + end + + def show_compile_error(error) do + Failure.show_compile_error(error) + |> IO.puts() + end + + def congratulate do + Notifications.congratulate() + |> IO.puts() + end + + def clear_screen do + GenServer.cast(__MODULE__, :clear_screen) + end + + defp format(failure, module, name) do + """ + #{Intro.intro(module, Tracker.visited())} + Now meditate upon #{format_module(module)} + #{ProgressBar.progress_bar(Tracker.summarize())} + ---------------------------------------- + #{name} + #{Failure.format_failure(failure)} + """ + end + + defp format_module(module) do + Module.split(module) |> List.last() + end +end diff --git a/swh/web/tests/resources/contents/code/extensions/test.f90 b/swh/web/tests/resources/contents/code/extensions/test.f90 new file mode 100644 index 00000000..1d41b40b --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.f90 @@ -0,0 +1,22 @@ +subroutine test_sub(k) + implicit none + + !=============================== + ! This is a test subroutine + !=============================== + + integer, intent(in) :: k + double precision, allocatable :: a(:) + integer, parameter :: nmax=10 + integer :: i + + allocate (a(nmax)) + + do i=1,nmax + a(i) = dble(i)*5.d0 + enddo + + print *, 'Hello world' + write (*,*) a(:) + +end subroutine test_sub diff --git a/swh/web/tests/resources/contents/code/extensions/test.feature b/swh/web/tests/resources/contents/code/extensions/test.feature new file mode 100644 index 00000000..52981fd1 --- /dev/null +++ b/swh/web/tests/resources/contents/code/extensions/test.feature @@ -0,0 +1,25 @@ +# language: en +Feature: Addition + In order to avoid silly mistakes + As a math idiot + I want to be told the sum of two numbers + + @this_is_a_tag + Scenario Outline: Add two numbers + * I have a calculator + Given I have entered into the calculator + And I have entered into the calculator + When I press