mirror of
https://github.com/yeslayla/run-gut-tests-action.git
synced 2025-01-13 12:33:41 +01:00
Intial commit
This commit is contained in:
commit
8f3b8cf989
15
.github/workflows/test_action.yml
vendored
Normal file
15
.github/workflows/test_action.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
name: Test Action
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
TestAction:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Run Action
|
||||||
|
id: run_tests
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
directory: test_proj
|
115
.gitignore
vendored
Normal file
115
.gitignore
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2020 Joseph Manley
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
action.yml
Normal file
16
action.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: "Run GUT Tests"
|
||||||
|
description: "Run GUT tests for a Godot project"
|
||||||
|
author: "josephbmanley"
|
||||||
|
inputs:
|
||||||
|
containerImage:
|
||||||
|
description: "The container to run tests inside of."
|
||||||
|
default: "barichello/godot-ci:latest"
|
||||||
|
directory:
|
||||||
|
description: "The name directory to run tests in."
|
||||||
|
required: false
|
||||||
|
runs:
|
||||||
|
using: "node12"
|
||||||
|
main: "dist/index.js"
|
||||||
|
branding:
|
||||||
|
icon: cpu
|
||||||
|
color: yellow
|
32984
dist/index.js
vendored
Normal file
32984
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dist/pagent.exe
vendored
Normal file
BIN
dist/pagent.exe
vendored
Normal file
Binary file not shown.
48
main.js
Normal file
48
main.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const core = require('@actions/core');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
// Setup Docker
|
||||||
|
const Docker = require('dockerode');
|
||||||
|
var docker = new Docker({socketPath: '/var/run/docker.sock'});
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get inputs
|
||||||
|
var docker_image = core.getInput('containerImage');
|
||||||
|
var work_dir = core.getInput('directory');
|
||||||
|
|
||||||
|
process.chdir(work_dir);
|
||||||
|
|
||||||
|
// Pull docker image for building
|
||||||
|
console.log("Pulling build image...");
|
||||||
|
docker.pull(docker_image, function(err, stream)
|
||||||
|
{
|
||||||
|
|
||||||
|
docker.modem.followProgress(stream, onFinished, onProgress);
|
||||||
|
|
||||||
|
// Wait to run build until after pull complete
|
||||||
|
function onFinished(err, output)
|
||||||
|
{
|
||||||
|
console.log("Starting image...")
|
||||||
|
docker.run(docker_image, ['godot', '-d', '-s', '--path /builder', 'addons/gut/gut_cmdln.gd'], process.stdout,
|
||||||
|
|
||||||
|
// Mount working directory to `/builder`
|
||||||
|
{ HostConfig: { Binds: [ process.cwd() + ":/builder" ] }},
|
||||||
|
|
||||||
|
function (err, data, container) {
|
||||||
|
|
||||||
|
if(err)
|
||||||
|
{
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function onProgress(event) {}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error.message);
|
||||||
|
}
|
233
package-lock.json
generated
Normal file
233
package-lock.json
generated
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/core": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg=="
|
||||||
|
},
|
||||||
|
"@zeit/ncc": {
|
||||||
|
"version": "0.22.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.22.3.tgz",
|
||||||
|
"integrity": "sha512-jnCLpLXWuw/PAiJiVbLjA8WBC0IJQbFeUwF4I9M+23MvIxTxk5pD4Q8byQBSPmHQjz5aBoA7AKAElQxMpjrCLQ=="
|
||||||
|
},
|
||||||
|
"asn1": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": "~2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base64-js": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||||
|
},
|
||||||
|
"bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||||
|
"requires": {
|
||||||
|
"tweetnacl": "^0.14.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bl": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
||||||
|
"requires": {
|
||||||
|
"buffer": "^5.5.0",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
|
||||||
|
"requires": {
|
||||||
|
"base64-js": "^1.0.2",
|
||||||
|
"ieee754": "^1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chownr": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"docker-modem": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-vDTzZjjO1sXMY7m0xKjGdFMMZL7vIUerkC3G4l6rnrpOET2M6AOufM8ajmQoOB+6RfSn6I/dlikCUq/Y91Q1sQ==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"readable-stream": "^3.5.0",
|
||||||
|
"split-ca": "^1.0.1",
|
||||||
|
"ssh2": "^0.8.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dockerode": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-XsSVB5Wu5HWMg1aelV5hFSqFJaKS5x1aiV/+sT7YOzOq1IRl49I/UwV8Pe4x6t0iF9kiGkWu5jwfvbkcFVupBw==",
|
||||||
|
"requires": {
|
||||||
|
"docker-modem": "^2.1.0",
|
||||||
|
"tar-fs": "~2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end-of-stream": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||||
|
"requires": {
|
||||||
|
"once": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fs-constants": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||||
|
},
|
||||||
|
"ieee754": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"mkdirp-classic": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
|
"requires": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pump": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||||
|
"requires": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||||
|
},
|
||||||
|
"safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"split-ca": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY="
|
||||||
|
},
|
||||||
|
"ssh2": {
|
||||||
|
"version": "0.8.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz",
|
||||||
|
"integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==",
|
||||||
|
"requires": {
|
||||||
|
"ssh2-streams": "~0.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ssh2-streams": {
|
||||||
|
"version": "0.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz",
|
||||||
|
"integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==",
|
||||||
|
"requires": {
|
||||||
|
"asn1": "~0.2.0",
|
||||||
|
"bcrypt-pbkdf": "^1.0.2",
|
||||||
|
"streamsearch": "~0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streamsearch": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
|
||||||
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tar-fs": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
|
||||||
|
"requires": {
|
||||||
|
"chownr": "^1.1.1",
|
||||||
|
"mkdirp-classic": "^0.5.2",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"tar-stream": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tar-stream": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==",
|
||||||
|
"requires": {
|
||||||
|
"bl": "^4.0.1",
|
||||||
|
"end-of-stream": "^1.4.1",
|
||||||
|
"fs-constants": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tweetnacl": {
|
||||||
|
"version": "0.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
|
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||||
|
},
|
||||||
|
"util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||||
|
},
|
||||||
|
"wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
package.json
Normal file
26
package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "run-gut-tests-action",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "dist/main.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"package": "ncc build main.js -o dist"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/josephbmanley/run-gut-tests-action.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/josephbmanley/run-gut-tests-action/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/josephbmanley/run-gut-tests-action#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/core": "^1.2.4",
|
||||||
|
"@zeit/ncc": "^0.22.3",
|
||||||
|
"dockerode": "^3.2.1"
|
||||||
|
}
|
||||||
|
}
|
10
test_proj/.gutconfig.json
Normal file
10
test_proj/.gutconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"dirs":[
|
||||||
|
"res://tests/"
|
||||||
|
],
|
||||||
|
"include_subdirs":true,
|
||||||
|
"ignore_pause":true,
|
||||||
|
"log_level":2,
|
||||||
|
"should_exit":true,
|
||||||
|
"should_maximize":false
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
source_md5="47313fa4c47a9963fddd764e1ec6e4a8"
|
||||||
|
dest_md5="2ded9e7f9060e2b530aab678b135fc5b"
|
||||||
|
|
BIN
test_proj/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex
Normal file
BIN
test_proj/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex
Normal file
Binary file not shown.
@ -0,0 +1,3 @@
|
|||||||
|
source_md5="eabe0e5cf43d82b1582b4d74e1bf4c67"
|
||||||
|
dest_md5="18b79d15b150af275df1433b5f4d7f18"
|
||||||
|
|
BIN
test_proj/.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex
Normal file
BIN
test_proj/.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex
Normal file
Binary file not shown.
40
test_proj/Gut.tscn
Normal file
40
test_proj/Gut.tscn
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://addons/gut/plugin_control.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[node name="Gut" type="Control"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
rect_min_size = Vector2( 740, 250 )
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
_font_name = "AnonymousPro"
|
||||||
|
_font_size = 20
|
||||||
|
_font_color = Color( 0.8, 0.8, 0.8, 1 )
|
||||||
|
_background_color = Color( 0.15, 0.15, 0.15, 1 )
|
||||||
|
_color_output = true
|
||||||
|
_select_script = ""
|
||||||
|
_tests_like = ""
|
||||||
|
_inner_class_name = ""
|
||||||
|
_run_on_load = true
|
||||||
|
_should_maximize = false
|
||||||
|
_should_print_to_console = true
|
||||||
|
_show_orphans = true
|
||||||
|
_log_level = 1
|
||||||
|
_yield_between_tests = true
|
||||||
|
_disable_strict_datatype_checks = false
|
||||||
|
_test_prefix = "test_"
|
||||||
|
_file_prefix = "test_"
|
||||||
|
_file_extension = ".gd"
|
||||||
|
_inner_class_prefix = "Test"
|
||||||
|
_temp_directory = "user://gut_temp_directory"
|
||||||
|
_export_path = ""
|
||||||
|
_include_subdirectories = true
|
||||||
|
_directory1 = "res://tests"
|
||||||
|
_directory2 = ""
|
||||||
|
_directory3 = ""
|
||||||
|
_directory4 = ""
|
||||||
|
_directory5 = ""
|
||||||
|
_directory6 = ""
|
||||||
|
_double_strategy = 1
|
||||||
|
_pre_run_script = ""
|
||||||
|
_post_run_script = ""
|
432
test_proj/addons/gut/GutScene.gd
Normal file
432
test_proj/addons/gut/GutScene.gd
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
extends Panel
|
||||||
|
|
||||||
|
onready var _script_list = $ScriptsList
|
||||||
|
onready var _nav = {
|
||||||
|
prev = $Navigation/Previous,
|
||||||
|
next = $Navigation/Next,
|
||||||
|
run = $Navigation/Run,
|
||||||
|
current_script = $Navigation/CurrentScript,
|
||||||
|
run_single = $Navigation/RunSingleScript
|
||||||
|
}
|
||||||
|
onready var _progress = {
|
||||||
|
script = $ScriptProgress,
|
||||||
|
script_xy = $ScriptProgress/xy,
|
||||||
|
test = $TestProgress,
|
||||||
|
test_xy = $TestProgress/xy
|
||||||
|
}
|
||||||
|
onready var _summary = {
|
||||||
|
failing = $Summary/Failing,
|
||||||
|
passing = $Summary/Passing,
|
||||||
|
fail_count = 0,
|
||||||
|
pass_count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
onready var _extras = $ExtraOptions
|
||||||
|
onready var _ignore_pauses = $ExtraOptions/IgnorePause
|
||||||
|
onready var _continue_button = $Continue/Continue
|
||||||
|
onready var _text_box = $TextDisplay/RichTextLabel
|
||||||
|
|
||||||
|
onready var _titlebar = {
|
||||||
|
bar = $TitleBar,
|
||||||
|
time = $TitleBar/Time,
|
||||||
|
label = $TitleBar/Title
|
||||||
|
}
|
||||||
|
|
||||||
|
onready var _user_files = $UserFileViewer
|
||||||
|
|
||||||
|
var _mouse = {
|
||||||
|
down = false,
|
||||||
|
in_title = false,
|
||||||
|
down_pos = null,
|
||||||
|
in_handle = false
|
||||||
|
}
|
||||||
|
var _is_running = false
|
||||||
|
var _start_time = 0.0
|
||||||
|
var _time = 0.0
|
||||||
|
|
||||||
|
const DEFAULT_TITLE = 'Gut: The Godot Unit Testing tool.'
|
||||||
|
var _pre_maximize_rect = null
|
||||||
|
var _font_size = 20
|
||||||
|
|
||||||
|
signal end_pause
|
||||||
|
signal ignore_pause
|
||||||
|
signal log_level_changed
|
||||||
|
signal run_script
|
||||||
|
signal run_single_script
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
|
||||||
|
if(Engine.editor_hint):
|
||||||
|
return
|
||||||
|
|
||||||
|
_pre_maximize_rect = get_rect()
|
||||||
|
_hide_scripts()
|
||||||
|
_update_controls()
|
||||||
|
_nav.current_script.set_text("No scripts available")
|
||||||
|
set_title()
|
||||||
|
clear_summary()
|
||||||
|
_titlebar.time.set_text("Time 0.0")
|
||||||
|
|
||||||
|
_extras.visible = false
|
||||||
|
update()
|
||||||
|
|
||||||
|
set_font_size(_font_size)
|
||||||
|
set_font('CourierPrime')
|
||||||
|
|
||||||
|
_user_files.set_position(Vector2(10, 30))
|
||||||
|
|
||||||
|
func elapsed_time_as_str():
|
||||||
|
return str("%.1f" % (_time / 1000.0), 's')
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
if(_is_running):
|
||||||
|
_time = OS.get_ticks_msec() - _start_time
|
||||||
|
_titlebar.time.set_text(str('Time: ', elapsed_time_as_str()))
|
||||||
|
|
||||||
|
func _draw(): # needs get_size()
|
||||||
|
# Draw the lines in the corner to show where you can
|
||||||
|
# drag to resize the dialog
|
||||||
|
var grab_margin = 3
|
||||||
|
var line_space = 3
|
||||||
|
var grab_line_color = Color(.4, .4, .4)
|
||||||
|
for i in range(1, 10):
|
||||||
|
var x = rect_size - Vector2(i * line_space, grab_margin)
|
||||||
|
var y = rect_size - Vector2(grab_margin, i * line_space)
|
||||||
|
draw_line(x, y, grab_line_color, 1, true)
|
||||||
|
|
||||||
|
func _on_Maximize_draw():
|
||||||
|
# draw the maximize square thing.
|
||||||
|
var btn = $TitleBar/Maximize
|
||||||
|
btn.set_text('')
|
||||||
|
var w = btn.get_size().x
|
||||||
|
var h = btn.get_size().y
|
||||||
|
btn.draw_rect(Rect2(0, 0, w, h), Color(0, 0, 0, 1))
|
||||||
|
btn.draw_rect(Rect2(2, 4, w - 4, h - 6), Color(1,1,1,1))
|
||||||
|
|
||||||
|
func _on_ShowExtras_draw():
|
||||||
|
var btn = $Continue/ShowExtras
|
||||||
|
btn.set_text('')
|
||||||
|
var start_x = 20
|
||||||
|
var start_y = 15
|
||||||
|
var pad = 5
|
||||||
|
var color = Color(.1, .1, .1, 1)
|
||||||
|
var width = 2
|
||||||
|
for i in range(3):
|
||||||
|
var y = start_y + pad * i
|
||||||
|
btn.draw_line(Vector2(start_x, y), Vector2(btn.get_size().x - start_x, y), color, width, true)
|
||||||
|
|
||||||
|
# ####################
|
||||||
|
# GUI Events
|
||||||
|
# ####################
|
||||||
|
func _on_Run_pressed():
|
||||||
|
_run_mode()
|
||||||
|
emit_signal('run_script', get_selected_index())
|
||||||
|
|
||||||
|
func _on_CurrentScript_pressed():
|
||||||
|
_toggle_scripts()
|
||||||
|
|
||||||
|
func _on_Previous_pressed():
|
||||||
|
_select_script(get_selected_index() - 1)
|
||||||
|
|
||||||
|
func _on_Next_pressed():
|
||||||
|
_select_script(get_selected_index() + 1)
|
||||||
|
|
||||||
|
func _on_LogLevelSlider_value_changed(_value):
|
||||||
|
emit_signal('log_level_changed', $LogLevelSlider.value)
|
||||||
|
|
||||||
|
func _on_Continue_pressed():
|
||||||
|
_continue_button.disabled = true
|
||||||
|
emit_signal('end_pause')
|
||||||
|
|
||||||
|
func _on_IgnorePause_pressed():
|
||||||
|
var checked = _ignore_pauses.is_pressed()
|
||||||
|
emit_signal('ignore_pause', checked)
|
||||||
|
if(checked):
|
||||||
|
emit_signal('end_pause')
|
||||||
|
_continue_button.disabled = true
|
||||||
|
|
||||||
|
func _on_RunSingleScript_pressed():
|
||||||
|
_run_mode()
|
||||||
|
emit_signal('run_single_script', get_selected_index())
|
||||||
|
|
||||||
|
func _on_ScriptsList_item_selected(index):
|
||||||
|
var tmr = $ScriptsList/DoubleClickTimer
|
||||||
|
if(!tmr.is_stopped()):
|
||||||
|
_run_mode()
|
||||||
|
emit_signal('run_single_script', get_selected_index())
|
||||||
|
tmr.stop()
|
||||||
|
else:
|
||||||
|
tmr.start()
|
||||||
|
|
||||||
|
_select_script(index)
|
||||||
|
|
||||||
|
func _on_TitleBar_mouse_entered():
|
||||||
|
_mouse.in_title = true
|
||||||
|
|
||||||
|
func _on_TitleBar_mouse_exited():
|
||||||
|
_mouse.in_title = false
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
if(event is InputEventMouseButton):
|
||||||
|
if(event.button_index == 1):
|
||||||
|
_mouse.down = event.pressed
|
||||||
|
if(_mouse.down):
|
||||||
|
_mouse.down_pos = event.position
|
||||||
|
|
||||||
|
if(_mouse.in_title):
|
||||||
|
if(event is InputEventMouseMotion and _mouse.down):
|
||||||
|
set_position(get_position() + (event.position - _mouse.down_pos))
|
||||||
|
_mouse.down_pos = event.position
|
||||||
|
_pre_maximize_rect = get_rect()
|
||||||
|
|
||||||
|
if(_mouse.in_handle):
|
||||||
|
if(event is InputEventMouseMotion and _mouse.down):
|
||||||
|
var new_size = rect_size + event.position - _mouse.down_pos
|
||||||
|
var new_mouse_down_pos = event.position
|
||||||
|
rect_size = new_size
|
||||||
|
_mouse.down_pos = new_mouse_down_pos
|
||||||
|
_pre_maximize_rect = get_rect()
|
||||||
|
|
||||||
|
func _on_ResizeHandle_mouse_entered():
|
||||||
|
_mouse.in_handle = true
|
||||||
|
|
||||||
|
func _on_ResizeHandle_mouse_exited():
|
||||||
|
_mouse.in_handle = false
|
||||||
|
|
||||||
|
func _on_RichTextLabel_gui_input(ev):
|
||||||
|
pass
|
||||||
|
# leaving this b/c it is wired up and might have to send
|
||||||
|
# more signals through
|
||||||
|
|
||||||
|
func _on_Copy_pressed():
|
||||||
|
OS.clipboard = _text_box.text
|
||||||
|
|
||||||
|
func _on_ShowExtras_toggled(button_pressed):
|
||||||
|
_extras.visible = button_pressed
|
||||||
|
|
||||||
|
func _on_Maximize_pressed():
|
||||||
|
if(get_rect() == _pre_maximize_rect):
|
||||||
|
maximize()
|
||||||
|
else:
|
||||||
|
rect_size = _pre_maximize_rect.size
|
||||||
|
rect_position = _pre_maximize_rect.position
|
||||||
|
# ####################
|
||||||
|
# Private
|
||||||
|
# ####################
|
||||||
|
func _run_mode(is_running=true):
|
||||||
|
if(is_running):
|
||||||
|
_start_time = OS.get_ticks_msec()
|
||||||
|
_time = 0.0
|
||||||
|
clear_summary()
|
||||||
|
_is_running = is_running
|
||||||
|
|
||||||
|
_hide_scripts()
|
||||||
|
var ctrls = $Navigation.get_children()
|
||||||
|
for i in range(ctrls.size()):
|
||||||
|
ctrls[i].disabled = is_running
|
||||||
|
|
||||||
|
func _select_script(index):
|
||||||
|
var text = _script_list.get_item_text(index)
|
||||||
|
var max_len = 50
|
||||||
|
if(text.length() > max_len):
|
||||||
|
text = '...' + text.right(text.length() - (max_len - 5))
|
||||||
|
$Navigation/CurrentScript.set_text(text)
|
||||||
|
_script_list.select(index)
|
||||||
|
_update_controls()
|
||||||
|
|
||||||
|
func _toggle_scripts():
|
||||||
|
if(_script_list.visible):
|
||||||
|
_hide_scripts()
|
||||||
|
else:
|
||||||
|
_show_scripts()
|
||||||
|
|
||||||
|
func _show_scripts():
|
||||||
|
_script_list.show()
|
||||||
|
|
||||||
|
func _hide_scripts():
|
||||||
|
_script_list.hide()
|
||||||
|
|
||||||
|
func _update_controls():
|
||||||
|
var is_empty = _script_list.get_selected_items().size() == 0
|
||||||
|
if(is_empty):
|
||||||
|
_nav.next.disabled = true
|
||||||
|
_nav.prev.disabled = true
|
||||||
|
else:
|
||||||
|
var index = get_selected_index()
|
||||||
|
_nav.prev.disabled = index <= 0
|
||||||
|
_nav.next.disabled = index >= _script_list.get_item_count() - 1
|
||||||
|
|
||||||
|
_nav.run.disabled = is_empty
|
||||||
|
_nav.current_script.disabled = is_empty
|
||||||
|
_nav.run_single.disabled = is_empty
|
||||||
|
|
||||||
|
func _update_summary():
|
||||||
|
if(!_summary):
|
||||||
|
return
|
||||||
|
|
||||||
|
var total = _summary.fail_count + _summary.pass_count
|
||||||
|
$Summary.visible = !total == 0
|
||||||
|
$Summary/AssertCount.text = str('Failures ', _summary.fail_count, '/', total)
|
||||||
|
# ####################
|
||||||
|
# Public
|
||||||
|
# ####################
|
||||||
|
func run_mode(is_running=true):
|
||||||
|
_run_mode(is_running)
|
||||||
|
|
||||||
|
func set_scripts(scripts):
|
||||||
|
_script_list.clear()
|
||||||
|
for i in range(scripts.size()):
|
||||||
|
_script_list.add_item(scripts[i])
|
||||||
|
_select_script(0)
|
||||||
|
_update_controls()
|
||||||
|
|
||||||
|
func select_script(index):
|
||||||
|
_select_script(index)
|
||||||
|
|
||||||
|
func get_selected_index():
|
||||||
|
return _script_list.get_selected_items()[0]
|
||||||
|
|
||||||
|
func get_log_level():
|
||||||
|
return $LogLevelSlider.value
|
||||||
|
|
||||||
|
func set_log_level(value):
|
||||||
|
var new_value = value
|
||||||
|
if(new_value == null):
|
||||||
|
new_value = 0
|
||||||
|
$LogLevelSlider.value = new_value
|
||||||
|
|
||||||
|
func set_ignore_pause(should):
|
||||||
|
_ignore_pauses.pressed = should
|
||||||
|
|
||||||
|
func get_ignore_pause():
|
||||||
|
return _ignore_pauses.pressed
|
||||||
|
|
||||||
|
func get_text_box():
|
||||||
|
# due to some timing issue, this cannot return _text_box but can return
|
||||||
|
# this.
|
||||||
|
return $TextDisplay/RichTextLabel
|
||||||
|
|
||||||
|
func end_run():
|
||||||
|
_run_mode(false)
|
||||||
|
_update_controls()
|
||||||
|
|
||||||
|
func set_progress_script_max(value):
|
||||||
|
var max_val = max(value, 1)
|
||||||
|
_progress.script.set_max(max_val)
|
||||||
|
_progress.script_xy.set_text(str('0/', max_val))
|
||||||
|
|
||||||
|
func set_progress_script_value(value):
|
||||||
|
_progress.script.set_value(value)
|
||||||
|
var txt = str(value, '/', _progress.test.get_max())
|
||||||
|
_progress.script_xy.set_text(txt)
|
||||||
|
|
||||||
|
func set_progress_test_max(value):
|
||||||
|
var max_val = max(value, 1)
|
||||||
|
_progress.test.set_max(max_val)
|
||||||
|
_progress.test_xy.set_text(str('0/', max_val))
|
||||||
|
|
||||||
|
func set_progress_test_value(value):
|
||||||
|
_progress.test.set_value(value)
|
||||||
|
var txt = str(value, '/', _progress.test.get_max())
|
||||||
|
_progress.test_xy.set_text(txt)
|
||||||
|
|
||||||
|
func clear_progress():
|
||||||
|
_progress.test.set_value(0)
|
||||||
|
_progress.script.set_value(0)
|
||||||
|
|
||||||
|
func pause():
|
||||||
|
_continue_button.disabled = false
|
||||||
|
|
||||||
|
func set_title(title=null):
|
||||||
|
if(title == null):
|
||||||
|
$TitleBar/Title.set_text(DEFAULT_TITLE)
|
||||||
|
else:
|
||||||
|
$TitleBar/Title.set_text(title)
|
||||||
|
|
||||||
|
func add_passing(amount=1):
|
||||||
|
if(!_summary):
|
||||||
|
return
|
||||||
|
_summary.pass_count += amount
|
||||||
|
_update_summary()
|
||||||
|
|
||||||
|
func add_failing(amount=1):
|
||||||
|
if(!_summary):
|
||||||
|
return
|
||||||
|
_summary.fail_count += amount
|
||||||
|
_update_summary()
|
||||||
|
|
||||||
|
func clear_summary():
|
||||||
|
_summary.fail_count = 0
|
||||||
|
_summary.pass_count = 0
|
||||||
|
_update_summary()
|
||||||
|
|
||||||
|
func maximize():
|
||||||
|
if(is_inside_tree()):
|
||||||
|
var vp_size_offset = get_viewport().size
|
||||||
|
rect_size = vp_size_offset / get_scale()
|
||||||
|
set_position(Vector2(0, 0))
|
||||||
|
|
||||||
|
func clear_text():
|
||||||
|
_text_box.bbcode_text = ''
|
||||||
|
|
||||||
|
func scroll_to_bottom():
|
||||||
|
pass
|
||||||
|
#_text_box.cursor_set_line(_gui.get_text_box().get_line_count())
|
||||||
|
|
||||||
|
func _set_font_size_for_rtl(rtl, new_size):
|
||||||
|
if(rtl.get('custom_fonts/normal_font') != null):
|
||||||
|
rtl.get('custom_fonts/bold_italics_font').size = new_size
|
||||||
|
rtl.get('custom_fonts/bold_font').size = new_size
|
||||||
|
rtl.get('custom_fonts/italics_font').size = new_size
|
||||||
|
rtl.get('custom_fonts/normal_font').size = new_size
|
||||||
|
|
||||||
|
|
||||||
|
func _set_fonts_for_rtl(rtl, base_font_name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func set_font_size(new_size):
|
||||||
|
_font_size = new_size
|
||||||
|
_set_font_size_for_rtl(_text_box, new_size)
|
||||||
|
_set_font_size_for_rtl(_user_files.get_rich_text_label(), new_size)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_font(rtl, font_name, custom_name):
|
||||||
|
if(font_name == null):
|
||||||
|
rtl.set('custom_fonts/' + custom_name, null)
|
||||||
|
else:
|
||||||
|
var dyn_font = DynamicFont.new()
|
||||||
|
var font_data = DynamicFontData.new()
|
||||||
|
font_data.font_path = 'res://addons/gut/fonts/' + font_name + '.ttf'
|
||||||
|
font_data.antialiased = true
|
||||||
|
dyn_font.font_data = font_data
|
||||||
|
rtl.set('custom_fonts/' + custom_name, dyn_font)
|
||||||
|
|
||||||
|
func _set_all_fonts_in_ftl(ftl, base_name):
|
||||||
|
if(base_name == 'Default'):
|
||||||
|
_set_font(ftl, null, 'normal_font')
|
||||||
|
_set_font(ftl, null, 'bold_font')
|
||||||
|
_set_font(ftl, null, 'italics_font')
|
||||||
|
_set_font(ftl, null, 'bold_italics_font')
|
||||||
|
else:
|
||||||
|
_set_font(ftl, base_name + '-Regular', 'normal_font')
|
||||||
|
_set_font(ftl, base_name + '-Bold', 'bold_font')
|
||||||
|
_set_font(ftl, base_name + '-Italic', 'italics_font')
|
||||||
|
_set_font(ftl, base_name + '-BoldItalic', 'bold_italics_font')
|
||||||
|
set_font_size(_font_size)
|
||||||
|
|
||||||
|
func set_font(base_name):
|
||||||
|
_set_all_fonts_in_ftl(_text_box, base_name)
|
||||||
|
_set_all_fonts_in_ftl(_user_files.get_rich_text_label(), base_name)
|
||||||
|
|
||||||
|
func set_default_font_color(color):
|
||||||
|
_text_box.set('custom_colors/default_color', color)
|
||||||
|
|
||||||
|
func set_background_color(color):
|
||||||
|
$TextDisplay.color = color
|
||||||
|
|
||||||
|
func _on_UserFiles_pressed():
|
||||||
|
_user_files.show_open()
|
||||||
|
|
||||||
|
func get_waiting_label():
|
||||||
|
return $TextDisplay/WaitingLabel
|
471
test_proj/addons/gut/GutScene.tscn
Normal file
471
test_proj/addons/gut/GutScene.tscn
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
[gd_scene load_steps=15 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://addons/gut/GutScene.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://addons/gut/fonts/AnonymousPro-Italic.ttf" type="DynamicFontData" id=2]
|
||||||
|
[ext_resource path="res://addons/gut/fonts/AnonymousPro-Regular.ttf" type="DynamicFontData" id=3]
|
||||||
|
[ext_resource path="res://addons/gut/fonts/AnonymousPro-BoldItalic.ttf" type="DynamicFontData" id=4]
|
||||||
|
[ext_resource path="res://addons/gut/fonts/AnonymousPro-Bold.ttf" type="DynamicFontData" id=5]
|
||||||
|
[ext_resource path="res://addons/gut/UserFileViewer.tscn" type="PackedScene" id=6]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id=1]
|
||||||
|
bg_color = Color( 0.192157, 0.192157, 0.227451, 1 )
|
||||||
|
corner_radius_top_left = 10
|
||||||
|
corner_radius_top_right = 10
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id=2]
|
||||||
|
bg_color = Color( 1, 1, 1, 1 )
|
||||||
|
border_color = Color( 0, 0, 0, 1 )
|
||||||
|
corner_radius_top_left = 5
|
||||||
|
corner_radius_top_right = 5
|
||||||
|
|
||||||
|
[sub_resource type="Theme" id=3]
|
||||||
|
resource_local_to_scene = true
|
||||||
|
Panel/styles/panel = SubResource( 2 )
|
||||||
|
Panel/styles/panelf = null
|
||||||
|
Panel/styles/panelnc = null
|
||||||
|
|
||||||
|
[sub_resource type="DynamicFont" id=4]
|
||||||
|
font_data = ExtResource( 4 )
|
||||||
|
|
||||||
|
[sub_resource type="DynamicFont" id=5]
|
||||||
|
font_data = ExtResource( 2 )
|
||||||
|
|
||||||
|
[sub_resource type="DynamicFont" id=6]
|
||||||
|
font_data = ExtResource( 5 )
|
||||||
|
|
||||||
|
[sub_resource type="DynamicFont" id=7]
|
||||||
|
font_data = ExtResource( 3 )
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id=8]
|
||||||
|
bg_color = Color( 0.192157, 0.192157, 0.227451, 1 )
|
||||||
|
corner_radius_top_left = 20
|
||||||
|
corner_radius_top_right = 20
|
||||||
|
|
||||||
|
[node name="Gut" type="Panel"]
|
||||||
|
margin_right = 880.0
|
||||||
|
margin_bottom = 360.0
|
||||||
|
rect_min_size = Vector2( 740, 250 )
|
||||||
|
custom_styles/panel = SubResource( 1 )
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="UserFileViewer" parent="." instance=ExtResource( 6 )]
|
||||||
|
margin_top = 388.0
|
||||||
|
margin_bottom = 818.0
|
||||||
|
|
||||||
|
[node name="TitleBar" type="Panel" parent="."]
|
||||||
|
anchor_top = -0.000491047
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = -0.000491047
|
||||||
|
margin_left = 1.0
|
||||||
|
margin_top = 1.17678
|
||||||
|
margin_right = -1.0
|
||||||
|
margin_bottom = 40.1768
|
||||||
|
theme = SubResource( 3 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_group_": true,
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="TitleBar"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
margin_bottom = 40.0
|
||||||
|
custom_colors/font_color = Color( 0, 0, 0, 1 )
|
||||||
|
text = "Gut"
|
||||||
|
align = 1
|
||||||
|
valign = 1
|
||||||
|
|
||||||
|
[node name="Time" type="Label" parent="TitleBar"]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
margin_left = -105.0
|
||||||
|
margin_right = -53.0
|
||||||
|
margin_bottom = 40.0
|
||||||
|
custom_colors/font_color = Color( 0, 0, 0, 1 )
|
||||||
|
text = "9999.99"
|
||||||
|
valign = 1
|
||||||
|
|
||||||
|
[node name="Maximize" type="Button" parent="TitleBar"]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
margin_left = -30.0
|
||||||
|
margin_top = 10.0
|
||||||
|
margin_right = -6.0
|
||||||
|
margin_bottom = 30.0
|
||||||
|
custom_colors/font_color = Color( 0, 0, 0, 1 )
|
||||||
|
text = "M"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="ScriptProgress" type="ProgressBar" parent="."]
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 75.0
|
||||||
|
margin_top = -70.0
|
||||||
|
margin_right = 185.0
|
||||||
|
margin_bottom = -40.0
|
||||||
|
hint_tooltip = "Overall progress of executing tests."
|
||||||
|
step = 1.0
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="ScriptProgress"]
|
||||||
|
margin_left = -70.0
|
||||||
|
margin_right = -5.0
|
||||||
|
margin_bottom = 30.0
|
||||||
|
text = "Scripts"
|
||||||
|
valign = 1
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="xy" type="Label" parent="ScriptProgress"]
|
||||||
|
visible = false
|
||||||
|
margin_right = 110.0
|
||||||
|
margin_bottom = 30.0
|
||||||
|
text = "0/0"
|
||||||
|
align = 1
|
||||||
|
valign = 1
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="TestProgress" type="ProgressBar" parent="."]
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 75.0
|
||||||
|
margin_top = -105.0
|
||||||
|
margin_right = 185.0
|
||||||
|
margin_bottom = -75.0
|
||||||
|
hint_tooltip = "Test progress for the current script."
|
||||||
|
step = 1.0
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="TestProgress"]
|
||||||
|
margin_left = -70.0
|
||||||
|
margin_right = -5.0
|
||||||
|
margin_bottom = 30.0
|
||||||
|
text = "Tests"
|
||||||
|
valign = 1
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="xy" type="Label" parent="TestProgress"]
|
||||||
|
visible = false
|
||||||
|
margin_right = 110.0
|
||||||
|
margin_bottom = 30.0
|
||||||
|
text = "0/0"
|
||||||
|
align = 1
|
||||||
|
valign = 1
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="TextDisplay" type="ColorRect" parent="."]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_top = 40.0
|
||||||
|
margin_bottom = -110.0
|
||||||
|
color = Color( 0, 0, 0, 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 10.0
|
||||||
|
focus_mode = 2
|
||||||
|
custom_fonts/bold_italics_font = SubResource( 4 )
|
||||||
|
custom_fonts/italics_font = SubResource( 5 )
|
||||||
|
custom_fonts/bold_font = SubResource( 6 )
|
||||||
|
custom_fonts/normal_font = SubResource( 7 )
|
||||||
|
bbcode_enabled = true
|
||||||
|
scroll_following = true
|
||||||
|
selection_enabled = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="WaitingLabel" type="RichTextLabel" parent="TextDisplay"]
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_top = -25.0
|
||||||
|
bbcode_enabled = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Navigation" type="Panel" parent="."]
|
||||||
|
self_modulate = Color( 1, 1, 1, 0 )
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 220.0
|
||||||
|
margin_top = -99.0
|
||||||
|
margin_right = 580.0
|
||||||
|
margin_bottom = 1.0
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Previous" type="Button" parent="Navigation"]
|
||||||
|
margin_left = -20.0
|
||||||
|
margin_top = 44.0
|
||||||
|
margin_right = 65.0
|
||||||
|
margin_bottom = 84.0
|
||||||
|
hint_tooltip = "Previous script in the list."
|
||||||
|
text = "|<"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Next" type="Button" parent="Navigation"]
|
||||||
|
margin_left = 250.0
|
||||||
|
margin_top = 44.0
|
||||||
|
margin_right = 335.0
|
||||||
|
margin_bottom = 84.0
|
||||||
|
hint_tooltip = "Next script in the list.
|
||||||
|
"
|
||||||
|
text = ">|"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Run" type="Button" parent="Navigation"]
|
||||||
|
margin_left = 70.0
|
||||||
|
margin_top = 44.0
|
||||||
|
margin_right = 155.0
|
||||||
|
margin_bottom = 84.0
|
||||||
|
hint_tooltip = "Run the currently selected item and all after it."
|
||||||
|
text = ">"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="CurrentScript" type="Button" parent="Navigation"]
|
||||||
|
anchor_top = -0.01
|
||||||
|
anchor_bottom = -0.01
|
||||||
|
margin_left = -20.0
|
||||||
|
margin_top = -5.0
|
||||||
|
margin_right = 335.0
|
||||||
|
margin_bottom = 35.0
|
||||||
|
hint_tooltip = "Select a script to run. You can run just this script, or this script and all scripts after using the run buttons."
|
||||||
|
text = "res://test/unit/test_gut.gd"
|
||||||
|
clip_text = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="RunSingleScript" type="Button" parent="Navigation"]
|
||||||
|
margin_left = 160.0
|
||||||
|
margin_top = 44.0
|
||||||
|
margin_right = 245.0
|
||||||
|
margin_bottom = 84.0
|
||||||
|
hint_tooltip = "Run the currently selected item.
|
||||||
|
|
||||||
|
If the selected item has Inner Test Classes
|
||||||
|
then they will all be run. If the selected item
|
||||||
|
is an Inner Test Class then only it will be run."
|
||||||
|
text = "> (1)"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="LogLevelSlider" type="HSlider" parent="."]
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 80.0
|
||||||
|
margin_top = -40.0
|
||||||
|
margin_right = 130.0
|
||||||
|
margin_bottom = -20.0
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
max_value = 2.0
|
||||||
|
tick_count = 3
|
||||||
|
ticks_on_borders = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="LogLevelSlider"]
|
||||||
|
margin_left = -37.0
|
||||||
|
margin_right = 28.0
|
||||||
|
margin_bottom = 40.0
|
||||||
|
rect_scale = Vector2( 0.5, 0.5 )
|
||||||
|
text = "Log Level"
|
||||||
|
valign = 1
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="ScriptsList" type="ItemList" parent="."]
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 179.0
|
||||||
|
margin_top = 40.0
|
||||||
|
margin_right = 619.0
|
||||||
|
margin_bottom = -110.0
|
||||||
|
allow_reselect = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="DoubleClickTimer" type="Timer" parent="ScriptsList"]
|
||||||
|
wait_time = 0.3
|
||||||
|
one_shot = true
|
||||||
|
|
||||||
|
[node name="ExtraOptions" type="Panel" parent="."]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = -212.0
|
||||||
|
margin_top = -260.0
|
||||||
|
margin_right = -2.0
|
||||||
|
margin_bottom = -106.0
|
||||||
|
custom_styles/panel = SubResource( 8 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="IgnorePause" type="CheckBox" parent="ExtraOptions"]
|
||||||
|
margin_left = 18.0
|
||||||
|
margin_right = 136.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
rect_scale = Vector2( 1.5, 1.5 )
|
||||||
|
hint_tooltip = "Ignore all calls to pause_before_teardown."
|
||||||
|
text = "Ignore Pauses"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Copy" type="Button" parent="ExtraOptions"]
|
||||||
|
margin_left = 15.0
|
||||||
|
margin_top = 40.0
|
||||||
|
margin_right = 195.0
|
||||||
|
margin_bottom = 80.0
|
||||||
|
hint_tooltip = "Copy all output to the clipboard."
|
||||||
|
text = "Copy to Clipboard"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="UserFiles" type="Button" parent="ExtraOptions"]
|
||||||
|
margin_left = 15.0
|
||||||
|
margin_top = 90.0
|
||||||
|
margin_right = 195.0
|
||||||
|
margin_bottom = 130.0
|
||||||
|
hint_tooltip = "Copy all output to the clipboard."
|
||||||
|
text = "View User Files"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="ResizeHandle" type="Control" parent="."]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = -40.0
|
||||||
|
margin_top = -40.0
|
||||||
|
|
||||||
|
[node name="Continue" type="Panel" parent="."]
|
||||||
|
self_modulate = Color( 1, 1, 1, 0 )
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = -150.0
|
||||||
|
margin_top = -100.0
|
||||||
|
margin_right = -30.0
|
||||||
|
margin_bottom = -10.0
|
||||||
|
|
||||||
|
[node name="Continue" type="Button" parent="Continue"]
|
||||||
|
margin_left = -2.0
|
||||||
|
margin_top = 45.0
|
||||||
|
margin_right = 117.0
|
||||||
|
margin_bottom = 85.0
|
||||||
|
hint_tooltip = "When a pause_before_teardown is encountered this button will be enabled and must be pressed to continue running tests."
|
||||||
|
disabled = true
|
||||||
|
text = "Continue"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="ShowExtras" type="Button" parent="Continue"]
|
||||||
|
anchor_left = -0.0166667
|
||||||
|
anchor_right = -0.0166667
|
||||||
|
margin_left = 50.0
|
||||||
|
margin_top = -5.0
|
||||||
|
margin_right = 120.0
|
||||||
|
margin_bottom = 35.0
|
||||||
|
rect_pivot_offset = Vector2( 35, 20 )
|
||||||
|
hint_tooltip = "Show/hide additional options."
|
||||||
|
toggle_mode = true
|
||||||
|
text = "_"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Summary" type="Node2D" parent="."]
|
||||||
|
position = Vector2( 0, 3 )
|
||||||
|
|
||||||
|
[node name="Passing" type="Label" parent="Summary"]
|
||||||
|
visible = false
|
||||||
|
margin_left = 5.0
|
||||||
|
margin_top = 7.0
|
||||||
|
margin_right = 45.0
|
||||||
|
margin_bottom = 21.0
|
||||||
|
custom_colors/font_color = Color( 0, 0, 0, 1 )
|
||||||
|
text = "0"
|
||||||
|
align = 1
|
||||||
|
valign = 1
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Failing" type="Label" parent="Summary"]
|
||||||
|
visible = false
|
||||||
|
margin_left = 100.0
|
||||||
|
margin_top = 7.0
|
||||||
|
margin_right = 140.0
|
||||||
|
margin_bottom = 21.0
|
||||||
|
custom_colors/font_color = Color( 0, 0, 0, 1 )
|
||||||
|
text = "0"
|
||||||
|
align = 1
|
||||||
|
valign = 1
|
||||||
|
|
||||||
|
[node name="AssertCount" type="Label" parent="Summary"]
|
||||||
|
margin_left = 5.0
|
||||||
|
margin_top = 7.0
|
||||||
|
margin_right = 165.0
|
||||||
|
margin_bottom = 21.0
|
||||||
|
custom_colors/font_color = Color( 0, 0, 0, 1 )
|
||||||
|
text = "Assert count"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
[connection signal="mouse_entered" from="TitleBar" to="." method="_on_TitleBar_mouse_entered"]
|
||||||
|
[connection signal="mouse_exited" from="TitleBar" to="." method="_on_TitleBar_mouse_exited"]
|
||||||
|
[connection signal="draw" from="TitleBar/Maximize" to="." method="_on_Maximize_draw"]
|
||||||
|
[connection signal="pressed" from="TitleBar/Maximize" to="." method="_on_Maximize_pressed"]
|
||||||
|
[connection signal="gui_input" from="TextDisplay/RichTextLabel" to="." method="_on_RichTextLabel_gui_input"]
|
||||||
|
[connection signal="pressed" from="Navigation/Previous" to="." method="_on_Previous_pressed"]
|
||||||
|
[connection signal="pressed" from="Navigation/Next" to="." method="_on_Next_pressed"]
|
||||||
|
[connection signal="pressed" from="Navigation/Run" to="." method="_on_Run_pressed"]
|
||||||
|
[connection signal="pressed" from="Navigation/CurrentScript" to="." method="_on_CurrentScript_pressed"]
|
||||||
|
[connection signal="pressed" from="Navigation/RunSingleScript" to="." method="_on_RunSingleScript_pressed"]
|
||||||
|
[connection signal="value_changed" from="LogLevelSlider" to="." method="_on_LogLevelSlider_value_changed"]
|
||||||
|
[connection signal="item_selected" from="ScriptsList" to="." method="_on_ScriptsList_item_selected"]
|
||||||
|
[connection signal="pressed" from="ExtraOptions/IgnorePause" to="." method="_on_IgnorePause_pressed"]
|
||||||
|
[connection signal="pressed" from="ExtraOptions/Copy" to="." method="_on_Copy_pressed"]
|
||||||
|
[connection signal="pressed" from="ExtraOptions/UserFiles" to="." method="_on_UserFiles_pressed"]
|
||||||
|
[connection signal="mouse_entered" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_entered"]
|
||||||
|
[connection signal="mouse_exited" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_exited"]
|
||||||
|
[connection signal="pressed" from="Continue/Continue" to="." method="_on_Continue_pressed"]
|
||||||
|
[connection signal="draw" from="Continue/ShowExtras" to="." method="_on_ShowExtras_draw"]
|
||||||
|
[connection signal="toggled" from="Continue/ShowExtras" to="." method="_on_ShowExtras_toggled"]
|
22
test_proj/addons/gut/LICENSE.md
Normal file
22
test_proj/addons/gut/LICENSE.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Copyright (c) 2018 Tom "Butch" Wesley
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
55
test_proj/addons/gut/UserFileViewer.gd
Normal file
55
test_proj/addons/gut/UserFileViewer.gd
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
extends WindowDialog
|
||||||
|
|
||||||
|
onready var rtl = $TextDisplay/RichTextLabel
|
||||||
|
var _has_opened_file = false
|
||||||
|
|
||||||
|
func _get_file_as_text(path):
|
||||||
|
var to_return = null
|
||||||
|
var f = File.new()
|
||||||
|
var result = f.open(path, f.READ)
|
||||||
|
if(result == OK):
|
||||||
|
to_return = f.get_as_text()
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
to_return = str('ERROR: Could not open file. Error code ', result)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
rtl.clear()
|
||||||
|
|
||||||
|
func _on_OpenFile_pressed():
|
||||||
|
$FileDialog.popup_centered()
|
||||||
|
|
||||||
|
func _on_FileDialog_file_selected(path):
|
||||||
|
show_file(path)
|
||||||
|
|
||||||
|
func _on_Close_pressed():
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
func show_file(path):
|
||||||
|
var text = _get_file_as_text(path)
|
||||||
|
if(text == ''):
|
||||||
|
text = '<Empty File>'
|
||||||
|
rtl.set_text(text)
|
||||||
|
self.window_title = path
|
||||||
|
|
||||||
|
func show_open():
|
||||||
|
self.popup_centered()
|
||||||
|
$FileDialog.popup_centered()
|
||||||
|
|
||||||
|
func _on_FileDialog_popup_hide():
|
||||||
|
if(rtl.text.length() == 0):
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
func get_rich_text_label():
|
||||||
|
return $TextDisplay/RichTextLabel
|
||||||
|
|
||||||
|
func _on_Home_pressed():
|
||||||
|
rtl.scroll_to_line(0)
|
||||||
|
|
||||||
|
func _on_End_pressed():
|
||||||
|
rtl.scroll_to_line(rtl.get_line_count() -1)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Copy_pressed():
|
||||||
|
OS.clipboard = rtl.text
|
127
test_proj/addons/gut/UserFileViewer.tscn
Normal file
127
test_proj/addons/gut/UserFileViewer.tscn
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://addons/gut/UserFileViewer.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[node name="UserFileViewer" type="WindowDialog"]
|
||||||
|
margin_top = 20.0
|
||||||
|
margin_right = 800.0
|
||||||
|
margin_bottom = 450.0
|
||||||
|
rect_min_size = Vector2( 800, 180 )
|
||||||
|
popup_exclusive = true
|
||||||
|
window_title = "View File"
|
||||||
|
resizable = true
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="FileDialog" type="FileDialog" parent="."]
|
||||||
|
margin_right = 416.0
|
||||||
|
margin_bottom = 184.0
|
||||||
|
rect_min_size = Vector2( 400, 140 )
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
popup_exclusive = true
|
||||||
|
window_title = "Open a File"
|
||||||
|
resizable = true
|
||||||
|
mode = 0
|
||||||
|
access = 1
|
||||||
|
show_hidden_files = true
|
||||||
|
current_dir = "user://"
|
||||||
|
current_path = "user://"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="TextDisplay" type="ColorRect" parent="."]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 8.0
|
||||||
|
margin_right = -10.0
|
||||||
|
margin_bottom = -65.0
|
||||||
|
color = Color( 0.2, 0.188235, 0.188235, 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
focus_mode = 2
|
||||||
|
text = "In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.
|
||||||
|
|
||||||
|
Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a first-century BCE text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin.
|
||||||
|
|
||||||
|
Versions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well."
|
||||||
|
selection_enabled = true
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="OpenFile" type="Button" parent="."]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = -158.0
|
||||||
|
margin_top = -50.0
|
||||||
|
margin_right = -84.0
|
||||||
|
margin_bottom = -30.0
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
text = "Open File"
|
||||||
|
|
||||||
|
[node name="Home" type="Button" parent="."]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = -478.0
|
||||||
|
margin_top = -50.0
|
||||||
|
margin_right = -404.0
|
||||||
|
margin_bottom = -30.0
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
text = "Home"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Copy" type="Button" parent="."]
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 160.0
|
||||||
|
margin_top = -50.0
|
||||||
|
margin_right = 234.0
|
||||||
|
margin_bottom = -30.0
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
text = "Copy"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="End" type="Button" parent="."]
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = -318.0
|
||||||
|
margin_top = -50.0
|
||||||
|
margin_right = -244.0
|
||||||
|
margin_bottom = -30.0
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
text = "End"
|
||||||
|
|
||||||
|
[node name="Close" type="Button" parent="."]
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
margin_left = 10.0
|
||||||
|
margin_top = -50.0
|
||||||
|
margin_right = 80.0
|
||||||
|
margin_bottom = -30.0
|
||||||
|
rect_scale = Vector2( 2, 2 )
|
||||||
|
text = "Close"
|
||||||
|
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
|
||||||
|
[connection signal="popup_hide" from="FileDialog" to="." method="_on_FileDialog_popup_hide"]
|
||||||
|
[connection signal="pressed" from="OpenFile" to="." method="_on_OpenFile_pressed"]
|
||||||
|
[connection signal="pressed" from="Home" to="." method="_on_Home_pressed"]
|
||||||
|
[connection signal="pressed" from="Copy" to="." method="_on_Copy_pressed"]
|
||||||
|
[connection signal="pressed" from="End" to="." method="_on_End_pressed"]
|
||||||
|
[connection signal="pressed" from="Close" to="." method="_on_Close_pressed"]
|
59
test_proj/addons/gut/autofree.gd
Normal file
59
test_proj/addons/gut/autofree.gd
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# Class used to keep track of objects to be freed and utilities to free them.
|
||||||
|
# ##############################################################################
|
||||||
|
var _to_free = []
|
||||||
|
var _to_queue_free = []
|
||||||
|
|
||||||
|
func add_free(thing):
|
||||||
|
if(typeof(thing) == TYPE_OBJECT):
|
||||||
|
if(!thing is Reference):
|
||||||
|
_to_free.append(thing)
|
||||||
|
|
||||||
|
func add_queue_free(thing):
|
||||||
|
_to_queue_free.append(thing)
|
||||||
|
|
||||||
|
func get_queue_free_count():
|
||||||
|
return _to_queue_free.size()
|
||||||
|
|
||||||
|
func get_free_count():
|
||||||
|
return _to_free.size()
|
||||||
|
|
||||||
|
func free_all():
|
||||||
|
for i in range(_to_free.size()):
|
||||||
|
if(is_instance_valid(_to_free[i])):
|
||||||
|
_to_free[i].free()
|
||||||
|
_to_free.clear()
|
||||||
|
|
||||||
|
for i in range(_to_queue_free.size()):
|
||||||
|
if(is_instance_valid(_to_queue_free[i])):
|
||||||
|
_to_queue_free[i].queue_free()
|
||||||
|
_to_queue_free.clear()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
|||||||
|
{func_decleration}
|
||||||
|
__gut_spy('{method_name}', {param_array})
|
||||||
|
if(__gut_should_call_super('{method_name}', {param_array})):
|
||||||
|
return {super_call}
|
||||||
|
else:
|
||||||
|
return __gut_get_stubbed_return('{method_name}', {param_array})
|
41
test_proj/addons/gut/double_templates/script_template.txt
Normal file
41
test_proj/addons/gut/double_templates/script_template.txt
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{extends}
|
||||||
|
|
||||||
|
var __gut_metadata_ = {
|
||||||
|
path = '{path}',
|
||||||
|
subpath = '{subpath}',
|
||||||
|
stubber = __gut_instance_from_id({stubber_id}),
|
||||||
|
spy = __gut_instance_from_id({spy_id}),
|
||||||
|
gut = __gut_instance_from_id({gut_id}),
|
||||||
|
}
|
||||||
|
|
||||||
|
func __gut_instance_from_id(inst_id):
|
||||||
|
if(inst_id == -1):
|
||||||
|
return null
|
||||||
|
else:
|
||||||
|
return instance_from_id(inst_id)
|
||||||
|
|
||||||
|
func __gut_should_call_super(method_name, called_with):
|
||||||
|
if(__gut_metadata_.stubber != null):
|
||||||
|
return __gut_metadata_.stubber.should_call_super(self, method_name, called_with)
|
||||||
|
else:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var __gut_utils_ = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
|
||||||
|
func __gut_spy(method_name, called_with):
|
||||||
|
if(__gut_metadata_.spy != null):
|
||||||
|
__gut_metadata_.spy.add_call(self, method_name, called_with)
|
||||||
|
|
||||||
|
func __gut_get_stubbed_return(method_name, called_with):
|
||||||
|
if(__gut_metadata_.stubber != null):
|
||||||
|
return __gut_metadata_.stubber.get_return(self, method_name, called_with)
|
||||||
|
else:
|
||||||
|
return null
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
if(__gut_metadata_.gut != null):
|
||||||
|
__gut_metadata_.gut.get_autofree().add_free(self)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Methods start here
|
||||||
|
# ------------------------------------------------------------------------------
|
556
test_proj/addons/gut/doubler.gd
Normal file
556
test_proj/addons/gut/doubler.gd
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Utility class to hold the local and built in methods separately. Add all local
|
||||||
|
# methods FIRST, then add built ins.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class ScriptMethods:
|
||||||
|
# List of methods that should not be overloaded when they are not defined
|
||||||
|
# in the class being doubled. These either break things if they are
|
||||||
|
# overloaded or do not have a "super" equivalent so we can't just pass
|
||||||
|
# through.
|
||||||
|
var _blacklist = [
|
||||||
|
'has_method',
|
||||||
|
'get_script',
|
||||||
|
'get',
|
||||||
|
'_notification',
|
||||||
|
'get_path',
|
||||||
|
'_enter_tree',
|
||||||
|
'_exit_tree',
|
||||||
|
'_process',
|
||||||
|
'_draw',
|
||||||
|
'_physics_process',
|
||||||
|
'_input',
|
||||||
|
'_unhandled_input',
|
||||||
|
'_unhandled_key_input',
|
||||||
|
'_set',
|
||||||
|
'_get', # probably
|
||||||
|
'emit_signal', # can't handle extra parameters to be sent with signal.
|
||||||
|
'draw_mesh', # issue with one parameter, value is `Null((..), (..), (..))``
|
||||||
|
'_to_string', # nonexistant function ._to_string
|
||||||
|
'_get_minimum_size', # Nonexistent function _get_minimum_size
|
||||||
|
]
|
||||||
|
|
||||||
|
# These methods should not be included in the double.
|
||||||
|
var _skip = [
|
||||||
|
# There is an init in the template. There is also no real reason
|
||||||
|
# to include this method since it will always be called, it has no
|
||||||
|
# return value, and you cannot prevent super from being called.
|
||||||
|
'_init'
|
||||||
|
]
|
||||||
|
|
||||||
|
var built_ins = []
|
||||||
|
var local_methods = []
|
||||||
|
var _method_names = []
|
||||||
|
|
||||||
|
func is_blacklisted(method_meta):
|
||||||
|
return _blacklist.find(method_meta.name) != -1
|
||||||
|
|
||||||
|
func _add_name_if_does_not_have(method_name):
|
||||||
|
if(_skip.has(method_name)):
|
||||||
|
return false
|
||||||
|
var should_add = _method_names.find(method_name) == -1
|
||||||
|
if(should_add):
|
||||||
|
_method_names.append(method_name)
|
||||||
|
return should_add
|
||||||
|
|
||||||
|
func add_built_in_method(method_meta):
|
||||||
|
var did_add = _add_name_if_does_not_have(method_meta.name)
|
||||||
|
if(did_add and !is_blacklisted(method_meta)):
|
||||||
|
built_ins.append(method_meta)
|
||||||
|
|
||||||
|
func add_local_method(method_meta):
|
||||||
|
var did_add = _add_name_if_does_not_have(method_meta.name)
|
||||||
|
if(did_add):
|
||||||
|
local_methods.append(method_meta)
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var text = "Locals\n"
|
||||||
|
for i in range(local_methods.size()):
|
||||||
|
text += str(" ", local_methods[i].name, "\n")
|
||||||
|
text += "Built-Ins\n"
|
||||||
|
for i in range(built_ins.size()):
|
||||||
|
text += str(" ", built_ins[i].name, "\n")
|
||||||
|
return text
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Helper class to deal with objects and inner classes.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class ObjectInfo:
|
||||||
|
var _path = null
|
||||||
|
var _subpaths = []
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
var _method_strategy = null
|
||||||
|
var make_partial_double = false
|
||||||
|
var scene_path = null
|
||||||
|
var _native_class = null
|
||||||
|
var _native_class_name = null
|
||||||
|
|
||||||
|
func _init(path, subpath=null):
|
||||||
|
_path = path
|
||||||
|
if(subpath != null):
|
||||||
|
_subpaths = _utils.split_string(subpath, '/')
|
||||||
|
|
||||||
|
# Returns an instance of the class/inner class
|
||||||
|
func instantiate():
|
||||||
|
var to_return = null
|
||||||
|
if(is_native()):
|
||||||
|
to_return = _native_class.new()
|
||||||
|
else:
|
||||||
|
to_return = get_loaded_class().new()
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# Can't call it get_class because that is reserved so it gets this ugly name.
|
||||||
|
# Loads up the class and then any inner classes to give back a reference to
|
||||||
|
# the desired Inner class (if there is any)
|
||||||
|
func get_loaded_class():
|
||||||
|
var LoadedClass = load(_path)
|
||||||
|
for i in range(_subpaths.size()):
|
||||||
|
LoadedClass = LoadedClass.get(_subpaths[i])
|
||||||
|
return LoadedClass
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
return str(_path, '[', get_subpath(), ']')
|
||||||
|
|
||||||
|
func get_path():
|
||||||
|
return _path
|
||||||
|
|
||||||
|
func get_subpath():
|
||||||
|
return _utils.join_array(_subpaths, '/')
|
||||||
|
|
||||||
|
func has_subpath():
|
||||||
|
return _subpaths.size() != 0
|
||||||
|
|
||||||
|
func get_extends_text():
|
||||||
|
var extend = null
|
||||||
|
if(is_native()):
|
||||||
|
var native = get_native_class_name()
|
||||||
|
if(native.begins_with('_')):
|
||||||
|
native = native.substr(1)
|
||||||
|
extend = str("extends ", native)
|
||||||
|
else:
|
||||||
|
extend = str("extends '", get_path(), "'")
|
||||||
|
|
||||||
|
if(has_subpath()):
|
||||||
|
extend += str('.', get_subpath().replace('/', '.'))
|
||||||
|
|
||||||
|
return extend
|
||||||
|
|
||||||
|
func get_method_strategy():
|
||||||
|
return _method_strategy
|
||||||
|
|
||||||
|
func set_method_strategy(method_strategy):
|
||||||
|
_method_strategy = method_strategy
|
||||||
|
|
||||||
|
func is_native():
|
||||||
|
return _native_class != null
|
||||||
|
|
||||||
|
func set_native_class(native_class):
|
||||||
|
_native_class = native_class
|
||||||
|
var inst = native_class.new()
|
||||||
|
_native_class_name = inst.get_class()
|
||||||
|
_path = _native_class_name
|
||||||
|
if(!inst is Reference):
|
||||||
|
inst.free()
|
||||||
|
|
||||||
|
func get_native_class_name():
|
||||||
|
return _native_class_name
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Allows for interacting with a file but only creating a string. This was done
|
||||||
|
# to ease the transition from files being created for doubles to loading
|
||||||
|
# doubles from a string. This allows the files to be created for debugging
|
||||||
|
# purposes since reading a file is easier than reading a dumped out string.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class FileOrString:
|
||||||
|
extends File
|
||||||
|
|
||||||
|
var _do_file = false
|
||||||
|
var _contents = ''
|
||||||
|
var _path = null
|
||||||
|
|
||||||
|
func open(path, mode):
|
||||||
|
_path = path
|
||||||
|
if(_do_file):
|
||||||
|
return .open(path, mode)
|
||||||
|
else:
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func close():
|
||||||
|
if(_do_file):
|
||||||
|
return .close()
|
||||||
|
|
||||||
|
func store_string(s):
|
||||||
|
if(_do_file):
|
||||||
|
.store_string(s)
|
||||||
|
_contents += s
|
||||||
|
|
||||||
|
func get_contents():
|
||||||
|
return _contents
|
||||||
|
|
||||||
|
func get_path():
|
||||||
|
return _path
|
||||||
|
|
||||||
|
func load_it():
|
||||||
|
if(_contents != ''):
|
||||||
|
var script = GDScript.new()
|
||||||
|
script.set_source_code(get_contents())
|
||||||
|
script.reload()
|
||||||
|
return script
|
||||||
|
else:
|
||||||
|
return load(_path)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# A stroke of genius if I do say so. This allows for doubling a scene without
|
||||||
|
# having to write any files. By overloading instance we can make whatever
|
||||||
|
# we want.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class PackedSceneDouble:
|
||||||
|
extends PackedScene
|
||||||
|
var _script = null
|
||||||
|
var _scene = null
|
||||||
|
|
||||||
|
func set_script_obj(obj):
|
||||||
|
_script = obj
|
||||||
|
|
||||||
|
func instance(edit_state=0):
|
||||||
|
var inst = _scene.instance(edit_state)
|
||||||
|
if(_script != null):
|
||||||
|
inst.set_script(_script)
|
||||||
|
return inst
|
||||||
|
|
||||||
|
func load_scene(path):
|
||||||
|
_scene = load(path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# START Doubler
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
|
||||||
|
var _ignored_methods = _utils.OneToMany.new()
|
||||||
|
var _stubber = _utils.Stubber.new()
|
||||||
|
var _lgr = _utils.get_logger()
|
||||||
|
var _method_maker = _utils.MethodMaker.new()
|
||||||
|
|
||||||
|
var _output_dir = 'user://gut_temp_directory'
|
||||||
|
var _double_count = 0 # used in making files names unique
|
||||||
|
var _spy = null
|
||||||
|
var _gut = null
|
||||||
|
var _strategy = null
|
||||||
|
var _base_script_text = _utils.get_file_as_text('res://addons/gut/double_templates/script_template.txt')
|
||||||
|
var _make_files = false
|
||||||
|
|
||||||
|
# These methods all call super implicitly. Stubbing them to call super causes
|
||||||
|
# super to be called twice.
|
||||||
|
var _non_super_methods = [
|
||||||
|
"_init",
|
||||||
|
"_ready",
|
||||||
|
"_notification",
|
||||||
|
"_enter_world",
|
||||||
|
"_exit_world",
|
||||||
|
"_process",
|
||||||
|
"_physics_process",
|
||||||
|
"_exit_tree",
|
||||||
|
"_gui_input ",
|
||||||
|
]
|
||||||
|
|
||||||
|
func _init(strategy=_utils.DOUBLE_STRATEGY.PARTIAL):
|
||||||
|
set_logger(_utils.get_logger())
|
||||||
|
_strategy = strategy
|
||||||
|
|
||||||
|
# ###############
|
||||||
|
# Private
|
||||||
|
# ###############
|
||||||
|
func _get_indented_line(indents, text):
|
||||||
|
var to_return = ''
|
||||||
|
for _i in range(indents):
|
||||||
|
to_return += "\t"
|
||||||
|
return str(to_return, text, "\n")
|
||||||
|
|
||||||
|
|
||||||
|
func _stub_to_call_super(obj_info, method_name):
|
||||||
|
if(_non_super_methods.has(method_name)):
|
||||||
|
return
|
||||||
|
var path = obj_info.get_path()
|
||||||
|
if(obj_info.scene_path != null):
|
||||||
|
path = obj_info.scene_path
|
||||||
|
var params = _utils.StubParams.new(path, method_name, obj_info.get_subpath())
|
||||||
|
params.to_call_super()
|
||||||
|
_stubber.add_stub(params)
|
||||||
|
|
||||||
|
func _get_base_script_text(obj_info, override_path):
|
||||||
|
var path = obj_info.get_path()
|
||||||
|
if(override_path != null):
|
||||||
|
path = override_path
|
||||||
|
|
||||||
|
var stubber_id = -1
|
||||||
|
if(_stubber != null):
|
||||||
|
stubber_id = _stubber.get_instance_id()
|
||||||
|
|
||||||
|
var spy_id = -1
|
||||||
|
if(_spy != null):
|
||||||
|
spy_id = _spy.get_instance_id()
|
||||||
|
|
||||||
|
var gut_id = -1
|
||||||
|
if(_gut != null):
|
||||||
|
gut_id = _gut.get_instance_id()
|
||||||
|
|
||||||
|
var values = {
|
||||||
|
"path":path,
|
||||||
|
"subpath":obj_info.get_subpath(),
|
||||||
|
"stubber_id":stubber_id,
|
||||||
|
"spy_id":spy_id,
|
||||||
|
"extends":obj_info.get_extends_text(),
|
||||||
|
"gut_id":gut_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return _base_script_text.format(values)
|
||||||
|
|
||||||
|
func _write_file(obj_info, dest_path, override_path=null):
|
||||||
|
var base_script = _get_base_script_text(obj_info, override_path)
|
||||||
|
var script_methods = _get_methods(obj_info)
|
||||||
|
|
||||||
|
var f = FileOrString.new()
|
||||||
|
f._do_file = _make_files
|
||||||
|
var f_result = f.open(dest_path, f.WRITE)
|
||||||
|
|
||||||
|
if(f_result != OK):
|
||||||
|
_lgr.error(str('Error creating file ', dest_path))
|
||||||
|
_lgr.error(str('Could not create double for :', obj_info.to_s()))
|
||||||
|
return
|
||||||
|
|
||||||
|
f.store_string(base_script)
|
||||||
|
|
||||||
|
for i in range(script_methods.local_methods.size()):
|
||||||
|
if(obj_info.make_partial_double):
|
||||||
|
_stub_to_call_super(obj_info, script_methods.local_methods[i].name)
|
||||||
|
f.store_string(_get_func_text(script_methods.local_methods[i]))
|
||||||
|
|
||||||
|
for i in range(script_methods.built_ins.size()):
|
||||||
|
_stub_to_call_super(obj_info, script_methods.built_ins[i].name)
|
||||||
|
f.store_string(_get_func_text(script_methods.built_ins[i]))
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
return f
|
||||||
|
|
||||||
|
func _double_scene_and_script(scene_info):
|
||||||
|
var to_return = PackedSceneDouble.new()
|
||||||
|
to_return.load_scene(scene_info.get_path())
|
||||||
|
|
||||||
|
var inst = load(scene_info.get_path()).instance()
|
||||||
|
var script_path = null
|
||||||
|
if(inst.get_script()):
|
||||||
|
script_path = inst.get_script().get_path()
|
||||||
|
inst.free()
|
||||||
|
|
||||||
|
if(script_path):
|
||||||
|
var oi = ObjectInfo.new(script_path)
|
||||||
|
oi.set_method_strategy(scene_info.get_method_strategy())
|
||||||
|
oi.make_partial_double = scene_info.make_partial_double
|
||||||
|
oi.scene_path = scene_info.get_path()
|
||||||
|
to_return.set_script_obj(_double(oi, scene_info.get_path()).load_it())
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func _get_methods(object_info):
|
||||||
|
var obj = object_info.instantiate()
|
||||||
|
# any method in the script or super script
|
||||||
|
var script_methods = ScriptMethods.new()
|
||||||
|
var methods = obj.get_method_list()
|
||||||
|
if(!(obj is Reference)):
|
||||||
|
obj.free()
|
||||||
|
|
||||||
|
# first pass is for local methods only
|
||||||
|
for i in range(methods.size()):
|
||||||
|
# 65 is a magic number for methods in script, though documentation
|
||||||
|
# says 64. This picks up local overloads of base class methods too.
|
||||||
|
if(methods[i].flags == 65 and !_ignored_methods.has(object_info.get_path(), methods[i]['name'])):
|
||||||
|
script_methods.add_local_method(methods[i])
|
||||||
|
|
||||||
|
|
||||||
|
if(object_info.get_method_strategy() == _utils.DOUBLE_STRATEGY.FULL):
|
||||||
|
# second pass is for anything not local
|
||||||
|
for i in range(methods.size()):
|
||||||
|
# 65 is a magic number for methods in script, though documentation
|
||||||
|
# says 64. This picks up local overloads of base class methods too.
|
||||||
|
if(methods[i].flags != 65 and !_ignored_methods.has(object_info.get_path(), methods[i]['name'])):
|
||||||
|
script_methods.add_built_in_method(methods[i])
|
||||||
|
|
||||||
|
return script_methods
|
||||||
|
|
||||||
|
func _get_inst_id_ref_str(inst):
|
||||||
|
var ref_str = 'null'
|
||||||
|
if(inst):
|
||||||
|
ref_str = str('instance_from_id(', inst.get_instance_id(),')')
|
||||||
|
return ref_str
|
||||||
|
|
||||||
|
func _get_func_text(method_hash):
|
||||||
|
return _method_maker.get_function_text(method_hash) + "\n"
|
||||||
|
|
||||||
|
# returns the path to write the double file to
|
||||||
|
func _get_temp_path(object_info):
|
||||||
|
var file_name = null
|
||||||
|
var extension = null
|
||||||
|
if(object_info.is_native()):
|
||||||
|
file_name = object_info.get_native_class_name()
|
||||||
|
extension = 'gd'
|
||||||
|
else:
|
||||||
|
file_name = object_info.get_path().get_file().get_basename()
|
||||||
|
extension = object_info.get_path().get_extension()
|
||||||
|
|
||||||
|
if(object_info.has_subpath()):
|
||||||
|
file_name += '__' + object_info.get_subpath().replace('/', '__')
|
||||||
|
|
||||||
|
file_name += str('__dbl', _double_count, '__.', extension)
|
||||||
|
|
||||||
|
var to_return = _output_dir.plus_file(file_name)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func _load_double(fileOrString):
|
||||||
|
return fileOrString.load_it()
|
||||||
|
|
||||||
|
func _double(obj_info, override_path=null):
|
||||||
|
var temp_path = _get_temp_path(obj_info)
|
||||||
|
var result = _write_file(obj_info, temp_path, override_path)
|
||||||
|
_double_count += 1
|
||||||
|
return result
|
||||||
|
|
||||||
|
func _double_script(path, make_partial, strategy):
|
||||||
|
var oi = ObjectInfo.new(path)
|
||||||
|
oi.make_partial_double = make_partial
|
||||||
|
oi.set_method_strategy(strategy)
|
||||||
|
return _double(oi).load_it()
|
||||||
|
|
||||||
|
func _double_inner(path, subpath, make_partial, strategy):
|
||||||
|
var oi = ObjectInfo.new(path, subpath)
|
||||||
|
oi.set_method_strategy(strategy)
|
||||||
|
oi.make_partial_double = make_partial
|
||||||
|
return _double(oi).load_it()
|
||||||
|
|
||||||
|
func _double_scene(path, make_partial, strategy):
|
||||||
|
var oi = ObjectInfo.new(path)
|
||||||
|
oi.set_method_strategy(strategy)
|
||||||
|
oi.make_partial_double = make_partial
|
||||||
|
return _double_scene_and_script(oi)
|
||||||
|
|
||||||
|
func _double_gdnative(native_class, make_partial, strategy):
|
||||||
|
var oi = ObjectInfo.new(null)
|
||||||
|
oi.set_native_class(native_class)
|
||||||
|
oi.set_method_strategy(strategy)
|
||||||
|
oi.make_partial_double = make_partial
|
||||||
|
return _double(oi).load_it()
|
||||||
|
|
||||||
|
# ###############
|
||||||
|
# Public
|
||||||
|
# ###############
|
||||||
|
func get_output_dir():
|
||||||
|
return _output_dir
|
||||||
|
|
||||||
|
func set_output_dir(output_dir):
|
||||||
|
if(output_dir != null):
|
||||||
|
_output_dir = output_dir
|
||||||
|
if(_make_files):
|
||||||
|
var d = Directory.new()
|
||||||
|
d.make_dir_recursive(output_dir)
|
||||||
|
|
||||||
|
func get_spy():
|
||||||
|
return _spy
|
||||||
|
|
||||||
|
func set_spy(spy):
|
||||||
|
_spy = spy
|
||||||
|
|
||||||
|
func get_stubber():
|
||||||
|
return _stubber
|
||||||
|
|
||||||
|
func set_stubber(stubber):
|
||||||
|
_stubber = stubber
|
||||||
|
|
||||||
|
func get_logger():
|
||||||
|
return _lgr
|
||||||
|
|
||||||
|
func set_logger(logger):
|
||||||
|
_lgr = logger
|
||||||
|
_method_maker.set_logger(logger)
|
||||||
|
|
||||||
|
func get_strategy():
|
||||||
|
return _strategy
|
||||||
|
|
||||||
|
func set_strategy(strategy):
|
||||||
|
_strategy = strategy
|
||||||
|
|
||||||
|
func get_gut():
|
||||||
|
return _gut
|
||||||
|
|
||||||
|
func set_gut(gut):
|
||||||
|
_gut = gut
|
||||||
|
|
||||||
|
func partial_double_scene(path, strategy=_strategy):
|
||||||
|
return _double_scene(path, true, strategy)
|
||||||
|
|
||||||
|
# double a scene
|
||||||
|
func double_scene(path, strategy=_strategy):
|
||||||
|
return _double_scene(path, false, strategy)
|
||||||
|
|
||||||
|
# double a script/object
|
||||||
|
func double(path, strategy=_strategy):
|
||||||
|
return _double_script(path, false, strategy)
|
||||||
|
|
||||||
|
func partial_double(path, strategy=_strategy):
|
||||||
|
return _double_script(path, true, strategy)
|
||||||
|
|
||||||
|
func partial_double_inner(path, subpath, strategy=_strategy):
|
||||||
|
return _double_inner(path, subpath, true, strategy)
|
||||||
|
|
||||||
|
# double an inner class in a script
|
||||||
|
func double_inner(path, subpath, strategy=_strategy):
|
||||||
|
return _double_inner(path, subpath, false, strategy)
|
||||||
|
|
||||||
|
# must always use FULL strategy since this is a native class and you won't get
|
||||||
|
# any methods if you don't use FULL
|
||||||
|
func double_gdnative(native_class):
|
||||||
|
return _double_gdnative(native_class, false, _utils.DOUBLE_STRATEGY.FULL)
|
||||||
|
|
||||||
|
# must always use FULL strategy since this is a native class and you won't get
|
||||||
|
# any methods if you don't use FULL
|
||||||
|
func partial_double_gdnative(native_class):
|
||||||
|
return _double_gdnative(native_class, true, _utils.DOUBLE_STRATEGY.FULL)
|
||||||
|
|
||||||
|
func clear_output_directory():
|
||||||
|
if(!_make_files):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var did = false
|
||||||
|
if(_output_dir.find('user://') == 0):
|
||||||
|
var d = Directory.new()
|
||||||
|
var result = d.open(_output_dir)
|
||||||
|
# BIG GOTCHA HERE. If it cannot open the dir w/ erro 31, then the
|
||||||
|
# directory becomes res:// and things go on normally and gut clears out
|
||||||
|
# out res:// which is SUPER BAD.
|
||||||
|
if(result == OK):
|
||||||
|
d.list_dir_begin(true)
|
||||||
|
var f = d.get_next()
|
||||||
|
while(f != ''):
|
||||||
|
d.remove(f)
|
||||||
|
f = d.get_next()
|
||||||
|
did = true
|
||||||
|
return did
|
||||||
|
|
||||||
|
func delete_output_directory():
|
||||||
|
var did = clear_output_directory()
|
||||||
|
if(did):
|
||||||
|
var d = Directory.new()
|
||||||
|
d.remove(_output_dir)
|
||||||
|
|
||||||
|
func add_ignored_method(path, method_name):
|
||||||
|
_ignored_methods.add(path, method_name)
|
||||||
|
|
||||||
|
func get_ignored_methods():
|
||||||
|
return _ignored_methods
|
||||||
|
|
||||||
|
func get_make_files():
|
||||||
|
return _make_files
|
||||||
|
|
||||||
|
func set_make_files(make_files):
|
||||||
|
_make_files = make_files
|
||||||
|
set_output_dir(_output_dir)
|
BIN
test_proj/addons/gut/fonts/AnonymousPro-Bold.ttf
Normal file
BIN
test_proj/addons/gut/fonts/AnonymousPro-Bold.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/AnonymousPro-BoldItalic.ttf
Normal file
BIN
test_proj/addons/gut/fonts/AnonymousPro-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/AnonymousPro-Italic.ttf
Normal file
BIN
test_proj/addons/gut/fonts/AnonymousPro-Italic.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/AnonymousPro-Regular.ttf
Normal file
BIN
test_proj/addons/gut/fonts/AnonymousPro-Regular.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/CourierPrime-Bold.ttf
Normal file
BIN
test_proj/addons/gut/fonts/CourierPrime-Bold.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/CourierPrime-BoldItalic.ttf
Normal file
BIN
test_proj/addons/gut/fonts/CourierPrime-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/CourierPrime-Italic.ttf
Normal file
BIN
test_proj/addons/gut/fonts/CourierPrime-Italic.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/CourierPrime-Regular.ttf
Normal file
BIN
test_proj/addons/gut/fonts/CourierPrime-Regular.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/LobsterTwo-Bold.ttf
Normal file
BIN
test_proj/addons/gut/fonts/LobsterTwo-Bold.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/LobsterTwo-BoldItalic.ttf
Normal file
BIN
test_proj/addons/gut/fonts/LobsterTwo-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/LobsterTwo-Italic.ttf
Normal file
BIN
test_proj/addons/gut/fonts/LobsterTwo-Italic.ttf
Normal file
Binary file not shown.
BIN
test_proj/addons/gut/fonts/LobsterTwo-Regular.ttf
Normal file
BIN
test_proj/addons/gut/fonts/LobsterTwo-Regular.ttf
Normal file
Binary file not shown.
94
test_proj/addons/gut/fonts/OFL.txt
Normal file
94
test_proj/addons/gut/fonts/OFL.txt
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),
|
||||||
|
with Reserved Font Name Anonymous Pro.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
1463
test_proj/addons/gut/gut.gd
Normal file
1463
test_proj/addons/gut/gut.gd
Normal file
File diff suppressed because it is too large
Load Diff
402
test_proj/addons/gut/gut_cmdln.gd
Normal file
402
test_proj/addons/gut/gut_cmdln.gd
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# Description
|
||||||
|
# -----------
|
||||||
|
# Command line interface for the GUT unit testing tool. Allows you to run tests
|
||||||
|
# from the command line instead of running a scene. Place this script along with
|
||||||
|
# gut.gd into your scripts directory at the root of your project. Once there you
|
||||||
|
# can run this script (from the root of your project) using the following command:
|
||||||
|
# godot -s -d test/gut/gut_cmdln.gd
|
||||||
|
#
|
||||||
|
# See the readme for a list of options and examples. You can also use the -gh
|
||||||
|
# option to get more information about how to use the command line interface.
|
||||||
|
# ##############################################################################
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var Optparse = load('res://addons/gut/optparse.gd')
|
||||||
|
var Gut = load('res://addons/gut/gut.gd')
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Helper class to resolve the various different places where an option can
|
||||||
|
# be set. Using the get_value method will enforce the order of precedence of:
|
||||||
|
# 1. command line value
|
||||||
|
# 2. config file value
|
||||||
|
# 3. default value
|
||||||
|
#
|
||||||
|
# The idea is that you set the base_opts. That will get you a copies of the
|
||||||
|
# hash with null values for the other types of values. Lower precedented hashes
|
||||||
|
# will punch through null values of higher precedented hashes.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class OptionResolver:
|
||||||
|
var base_opts = null
|
||||||
|
var cmd_opts = null
|
||||||
|
var config_opts = null
|
||||||
|
|
||||||
|
|
||||||
|
func get_value(key):
|
||||||
|
return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))
|
||||||
|
|
||||||
|
func set_base_opts(opts):
|
||||||
|
base_opts = opts
|
||||||
|
cmd_opts = _null_copy(opts)
|
||||||
|
config_opts = _null_copy(opts)
|
||||||
|
|
||||||
|
# creates a copy of a hash with all values null.
|
||||||
|
func _null_copy(h):
|
||||||
|
var new_hash = {}
|
||||||
|
for key in h:
|
||||||
|
new_hash[key] = null
|
||||||
|
return new_hash
|
||||||
|
|
||||||
|
func _nvl(a, b):
|
||||||
|
if(a == null):
|
||||||
|
return b
|
||||||
|
else:
|
||||||
|
return a
|
||||||
|
func _string_it(h):
|
||||||
|
var to_return = ''
|
||||||
|
for key in h:
|
||||||
|
to_return += str('(',key, ':', _nvl(h[key], 'NULL'), ')')
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
return str("base:\n", _string_it(base_opts), "\n", \
|
||||||
|
"config:\n", _string_it(config_opts), "\n", \
|
||||||
|
"cmd:\n", _string_it(cmd_opts), "\n", \
|
||||||
|
"resolved:\n", _string_it(get_resolved_values()))
|
||||||
|
|
||||||
|
func get_resolved_values():
|
||||||
|
var to_return = {}
|
||||||
|
for key in base_opts:
|
||||||
|
to_return[key] = get_value(key)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func to_s_verbose():
|
||||||
|
var to_return = ''
|
||||||
|
var resolved = get_resolved_values()
|
||||||
|
for key in base_opts:
|
||||||
|
to_return += str(key, "\n")
|
||||||
|
to_return += str(' default: ', _nvl(base_opts[key], 'NULL'), "\n")
|
||||||
|
to_return += str(' config: ', _nvl(config_opts[key], ' --'), "\n")
|
||||||
|
to_return += str(' cmd: ', _nvl(cmd_opts[key], ' --'), "\n")
|
||||||
|
to_return += str(' final: ', _nvl(resolved[key], 'NULL'), "\n")
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Here starts the actual script that uses the Options class to kick off Gut
|
||||||
|
# and run your tests.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
# instance of gut
|
||||||
|
var _tester = null
|
||||||
|
# array of command line options specified
|
||||||
|
var _final_opts = []
|
||||||
|
# Hash for easier access to the options in the code. Options will be
|
||||||
|
# extracted into this hash and then the hash will be used afterwards so
|
||||||
|
# that I don't make any dumb typos and get the neat code-sense when I
|
||||||
|
# type a dot.
|
||||||
|
var options = {
|
||||||
|
background_color = Color(.15, .15, .15, 1).to_html(),
|
||||||
|
config_file = 'res://.gutconfig.json',
|
||||||
|
dirs = [],
|
||||||
|
disable_colors = false,
|
||||||
|
double_strategy = 'partial',
|
||||||
|
font_color = Color(.8, .8, .8, 1).to_html(),
|
||||||
|
font_name = 'CourierPrime',
|
||||||
|
font_size = 16,
|
||||||
|
hide_orphans = false,
|
||||||
|
ignore_pause = false,
|
||||||
|
include_subdirs = false,
|
||||||
|
inner_class = '',
|
||||||
|
log_level = 1,
|
||||||
|
opacity = 100,
|
||||||
|
post_run_script = '',
|
||||||
|
pre_run_script = '',
|
||||||
|
prefix = 'test_',
|
||||||
|
selected = '',
|
||||||
|
should_exit = false,
|
||||||
|
should_exit_on_success = false,
|
||||||
|
should_maximize = false,
|
||||||
|
show_help = false,
|
||||||
|
suffix = '.gd',
|
||||||
|
tests = [],
|
||||||
|
unit_test_name = '',
|
||||||
|
}
|
||||||
|
var valid_fonts = ['AnonymousPro', 'CourierPro', 'LobsterTwo', 'Default']
|
||||||
|
|
||||||
|
# flag to indicate if only a single script should be run.
|
||||||
|
var _run_single = false
|
||||||
|
|
||||||
|
|
||||||
|
func setup_options():
|
||||||
|
var opts = Optparse.new()
|
||||||
|
opts.set_banner(('This is the command line interface for the unit testing tool Gut. With this ' +
|
||||||
|
'interface you can run one or more test scripts from the command line. In order ' +
|
||||||
|
'for the Gut options to not clash with any other godot options, each option starts ' +
|
||||||
|
'with a "g". Also, any option that requires a value will take the form of ' +
|
||||||
|
'"-g<name>=<value>". There cannot be any spaces between the option, the "=", or ' +
|
||||||
|
'inside a specified value or godot will think you are trying to run a scene.'))
|
||||||
|
opts.add('-gtest', [], 'Comma delimited list of full paths to test scripts to run.')
|
||||||
|
opts.add('-gdir', options.dirs, 'Comma delimited list of directories to add tests from.')
|
||||||
|
opts.add('-gprefix', options.prefix, 'Prefix used to find tests when specifying -gdir. Default "[default]".')
|
||||||
|
opts.add('-gsuffix', options.suffix, 'Suffix used to find tests when specifying -gdir. Default "[default]".')
|
||||||
|
opts.add('-ghide_orphans', false, 'Display orphan counts for tests and scripts. Default "[default]".')
|
||||||
|
opts.add('-gmaximize', false, 'Maximizes test runner window to fit the viewport.')
|
||||||
|
opts.add('-gexit', false, 'Exit after running tests. If not specified you have to manually close the window.')
|
||||||
|
opts.add('-gexit_on_success', false, 'Only exit if all tests pass.')
|
||||||
|
opts.add('-glog', options.log_level, 'Log level. Default [default]')
|
||||||
|
opts.add('-gignore_pause', false, 'Ignores any calls to gut.pause_before_teardown.')
|
||||||
|
opts.add('-gselect', '', ('Select a script to run initially. The first script that ' +
|
||||||
|
'was loaded using -gtest or -gdir that contains the specified ' +
|
||||||
|
'string will be executed. You may run others by interacting ' +
|
||||||
|
'with the GUI.'))
|
||||||
|
opts.add('-gunit_test_name', '', ('Name of a test to run. Any test that contains the specified ' +
|
||||||
|
'text will be run, all others will be skipped.'))
|
||||||
|
opts.add('-gh', false, 'Print this help, then quit')
|
||||||
|
opts.add('-gconfig', 'res://.gutconfig.json', 'A config file that contains configuration information. Default is res://.gutconfig.json')
|
||||||
|
opts.add('-ginner_class', '', 'Only run inner classes that contain this string')
|
||||||
|
opts.add('-gopacity', options.opacity, 'Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque.')
|
||||||
|
opts.add('-gpo', false, 'Print option values from all sources and the value used, then quit.')
|
||||||
|
opts.add('-ginclude_subdirs', false, 'Include subdirectories of -gdir.')
|
||||||
|
opts.add('-gdouble_strategy', 'partial', 'Default strategy to use when doubling. Valid values are [partial, full]. Default "[default]"')
|
||||||
|
opts.add('-gdisable_colors', false, 'Disable command line colors.')
|
||||||
|
opts.add('-gpre_run_script', '', 'pre-run hook script path')
|
||||||
|
opts.add('-gpost_run_script', '', 'post-run hook script path')
|
||||||
|
opts.add('-gprint_gutconfig_sample', false, 'Print out json that can be used to make a gutconfig file then quit.')
|
||||||
|
|
||||||
|
opts.add('-gfont_name', options.font_name, str('Valid values are: ', valid_fonts, '. Default "[default]"'))
|
||||||
|
opts.add('-gfont_size', options.font_size, 'Font size, default "[default]"')
|
||||||
|
opts.add('-gbackground_color', options.background_color, 'Background color as an html color, default "[default]"')
|
||||||
|
opts.add('-gfont_color',options.font_color, 'Font color as an html color, default "[default]"')
|
||||||
|
return opts
|
||||||
|
|
||||||
|
|
||||||
|
# Parses options, applying them to the _tester or setting values
|
||||||
|
# in the options struct.
|
||||||
|
func extract_command_line_options(from, to):
|
||||||
|
to.config_file = from.get_value('-gconfig')
|
||||||
|
to.dirs = from.get_value('-gdir')
|
||||||
|
to.disable_colors = from.get_value('-gdisable_colors')
|
||||||
|
to.double_strategy = from.get_value('-gdouble_strategy')
|
||||||
|
to.ignore_pause = from.get_value('-gignore_pause')
|
||||||
|
to.include_subdirs = from.get_value('-ginclude_subdirs')
|
||||||
|
to.inner_class = from.get_value('-ginner_class')
|
||||||
|
to.log_level = from.get_value('-glog')
|
||||||
|
to.opacity = from.get_value('-gopacity')
|
||||||
|
to.post_run_script = from.get_value('-gpost_run_script')
|
||||||
|
to.pre_run_script = from.get_value('-gpre_run_script')
|
||||||
|
to.prefix = from.get_value('-gprefix')
|
||||||
|
to.selected = from.get_value('-gselect')
|
||||||
|
to.should_exit = from.get_value('-gexit')
|
||||||
|
to.should_exit_on_success = from.get_value('-gexit_on_success')
|
||||||
|
to.should_maximize = from.get_value('-gmaximize')
|
||||||
|
to.hide_orphans = from.get_value('-ghide_orphans')
|
||||||
|
to.suffix = from.get_value('-gsuffix')
|
||||||
|
to.tests = from.get_value('-gtest')
|
||||||
|
to.unit_test_name = from.get_value('-gunit_test_name')
|
||||||
|
|
||||||
|
to.font_size = from.get_value('-gfont_size')
|
||||||
|
to.font_name = from.get_value('-gfont_name')
|
||||||
|
to.background_color = from.get_value('-gbackground_color')
|
||||||
|
to.font_color = from.get_value('-gfont_color')
|
||||||
|
|
||||||
|
|
||||||
|
func load_options_from_config_file(file_path, into):
|
||||||
|
# SHORTCIRCUIT
|
||||||
|
var f = File.new()
|
||||||
|
if(!f.file_exists(file_path)):
|
||||||
|
if(file_path != 'res://.gutconfig.json'):
|
||||||
|
print('ERROR: Config File "', file_path, '" does not exist.')
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
f.open(file_path, f.READ)
|
||||||
|
var json = f.get_as_text()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
var results = JSON.parse(json)
|
||||||
|
# SHORTCIRCUIT
|
||||||
|
if(results.error != OK):
|
||||||
|
print("\n\n",'!! ERROR parsing file: ', file_path)
|
||||||
|
print(' at line ', results.error_line, ':')
|
||||||
|
print(' ', results.error_string)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# Get all the options out of the config file using the option name. The
|
||||||
|
# options hash is now the default source of truth for the name of an option.
|
||||||
|
for key in into:
|
||||||
|
if(results.result.has(key)):
|
||||||
|
into[key] = results.result[key]
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# Apply all the options specified to _tester. This is where the rubber meets
|
||||||
|
# the road.
|
||||||
|
func apply_options(opts):
|
||||||
|
_tester = Gut.new()
|
||||||
|
get_root().add_child(_tester)
|
||||||
|
_tester.connect('tests_finished', self, '_on_tests_finished', [opts.should_exit, opts.should_exit_on_success])
|
||||||
|
_tester.set_yield_between_tests(true)
|
||||||
|
_tester.set_modulate(Color(1.0, 1.0, 1.0, min(1.0, float(opts.opacity) / 100)))
|
||||||
|
_tester.show()
|
||||||
|
|
||||||
|
_tester.set_include_subdirectories(opts.include_subdirs)
|
||||||
|
|
||||||
|
if(opts.should_maximize):
|
||||||
|
_tester.maximize()
|
||||||
|
|
||||||
|
if(opts.inner_class != ''):
|
||||||
|
_tester.set_inner_class_name(opts.inner_class)
|
||||||
|
_tester.set_log_level(opts.log_level)
|
||||||
|
_tester.set_ignore_pause_before_teardown(opts.ignore_pause)
|
||||||
|
|
||||||
|
for i in range(opts.dirs.size()):
|
||||||
|
_tester.add_directory(opts.dirs[i], opts.prefix, opts.suffix)
|
||||||
|
|
||||||
|
for i in range(opts.tests.size()):
|
||||||
|
_tester.add_script(opts.tests[i])
|
||||||
|
|
||||||
|
if(opts.selected != ''):
|
||||||
|
_tester.select_script(opts.selected)
|
||||||
|
_run_single = true
|
||||||
|
|
||||||
|
if(opts.double_strategy == 'full'):
|
||||||
|
_tester.set_double_strategy(_utils.DOUBLE_STRATEGY.FULL)
|
||||||
|
elif(opts.double_strategy == 'partial'):
|
||||||
|
_tester.set_double_strategy(_utils.DOUBLE_STRATEGY.PARTIAL)
|
||||||
|
|
||||||
|
_tester.set_unit_test_name(opts.unit_test_name)
|
||||||
|
_tester.set_pre_run_script(opts.pre_run_script)
|
||||||
|
_tester.set_post_run_script(opts.post_run_script)
|
||||||
|
_tester.set_color_output(!opts.disable_colors)
|
||||||
|
_tester.show_orphans(!opts.hide_orphans)
|
||||||
|
|
||||||
|
_tester.get_gui().set_font_size(opts.font_size)
|
||||||
|
_tester.get_gui().set_font(opts.font_name)
|
||||||
|
if(opts.font_color != null and opts.font_color.is_valid_html_color()):
|
||||||
|
_tester.get_gui().set_default_font_color(Color(opts.font_color))
|
||||||
|
if(opts.background_color != null and opts.background_color.is_valid_html_color()):
|
||||||
|
_tester.get_gui().set_background_color(Color(opts.background_color))
|
||||||
|
|
||||||
|
|
||||||
|
func _print_gutconfigs(values):
|
||||||
|
var header = """Here is a sample of a full .gutconfig.json file.
|
||||||
|
You do not need to specify all values in your own file. The values supplied in
|
||||||
|
this sample are what would be used if you ran gut w/o the -gprint_gutconfig_sample
|
||||||
|
option (the resolved values where default < .gutconfig < command line)."""
|
||||||
|
print("\n", header.replace("\n", ' '), "\n\n")
|
||||||
|
var resolved = values
|
||||||
|
|
||||||
|
# remove some options that don't make sense to be in config
|
||||||
|
resolved.erase("config_file")
|
||||||
|
resolved.erase("show_help")
|
||||||
|
|
||||||
|
print("Here's a config with all the properties set based off of your current command and config.")
|
||||||
|
var text = JSON.print(resolved)
|
||||||
|
print(text.replace(',', ",\n"))
|
||||||
|
|
||||||
|
for key in resolved:
|
||||||
|
resolved[key] = null
|
||||||
|
|
||||||
|
print("\n\nAnd here's an empty config for you fill in what you want.")
|
||||||
|
text = JSON.print(resolved)
|
||||||
|
print(text.replace(',', ",\n"))
|
||||||
|
|
||||||
|
|
||||||
|
# parse options and run Gut
|
||||||
|
func _run_gut():
|
||||||
|
var opt_resolver = OptionResolver.new()
|
||||||
|
opt_resolver.set_base_opts(options)
|
||||||
|
|
||||||
|
print("\n\n", ' --- Gut ---')
|
||||||
|
var o = setup_options()
|
||||||
|
|
||||||
|
var all_options_valid = o.parse()
|
||||||
|
extract_command_line_options(o, opt_resolver.cmd_opts)
|
||||||
|
var load_result = \
|
||||||
|
load_options_from_config_file(opt_resolver.get_value('config_file'), opt_resolver.config_opts)
|
||||||
|
|
||||||
|
if(load_result == -1): # -1 indicates json parse error
|
||||||
|
quit()
|
||||||
|
else:
|
||||||
|
if(!all_options_valid):
|
||||||
|
quit()
|
||||||
|
elif(o.get_value('-gh')):
|
||||||
|
print(_utils.get_version_text())
|
||||||
|
o.print_help()
|
||||||
|
quit()
|
||||||
|
elif(o.get_value('-gpo')):
|
||||||
|
print('All command line options and where they are specified. ' +
|
||||||
|
'The "final" value shows which value will actually be used ' +
|
||||||
|
'based on order of precedence (default < .gutconfig < cmd line).' + "\n")
|
||||||
|
print(opt_resolver.to_s_verbose())
|
||||||
|
quit()
|
||||||
|
elif(o.get_value('-gprint_gutconfig_sample')):
|
||||||
|
_print_gutconfigs(opt_resolver.get_resolved_values())
|
||||||
|
quit()
|
||||||
|
else:
|
||||||
|
_final_opts = opt_resolver.get_resolved_values();
|
||||||
|
apply_options(_final_opts)
|
||||||
|
_tester.test_scripts(!_run_single)
|
||||||
|
|
||||||
|
|
||||||
|
# exit if option is set.
|
||||||
|
func _on_tests_finished(should_exit, should_exit_on_success):
|
||||||
|
if(_final_opts.dirs.size() == 0):
|
||||||
|
if(_tester.get_summary().get_totals().scripts == 0):
|
||||||
|
var lgr = _tester.get_logger()
|
||||||
|
lgr.error('No directories configured. Add directories with options or a .gutconfig.json file. Use the -gh option for more information.')
|
||||||
|
|
||||||
|
if(_tester.get_fail_count()):
|
||||||
|
OS.exit_code = 1
|
||||||
|
|
||||||
|
# Overwrite the exit code with the post_script
|
||||||
|
var post_inst = _tester.get_post_run_script_instance()
|
||||||
|
if(post_inst != null and post_inst.get_exit_code() != null):
|
||||||
|
OS.exit_code = post_inst.get_exit_code()
|
||||||
|
|
||||||
|
if(should_exit or (should_exit_on_success and _tester.get_fail_count() == 0)):
|
||||||
|
quit()
|
||||||
|
else:
|
||||||
|
print("Tests finished, exit manually")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# MAIN
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func _init():
|
||||||
|
if(!_utils.is_version_ok()):
|
||||||
|
print("\n\n", _utils.get_version_text())
|
||||||
|
push_error(_utils.get_bad_version_text())
|
||||||
|
OS.exit_code = 1
|
||||||
|
quit()
|
||||||
|
else:
|
||||||
|
_run_gut()
|
12
test_proj/addons/gut/gut_plugin.gd
Normal file
12
test_proj/addons/gut/gut_plugin.gd
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
# Initialization of the plugin goes here
|
||||||
|
# Add the new type with a name, a parent type, a script and an icon
|
||||||
|
add_custom_type("Gut", "Control", preload("plugin_control.gd"), preload("icon.png"))
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
# Clean-up of the plugin goes here
|
||||||
|
# Always remember to remove it from the engine when deactivated
|
||||||
|
remove_custom_type("Gut")
|
35
test_proj/addons/gut/hook_script.gd
Normal file
35
test_proj/addons/gut/hook_script.gd
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# This script is the base for custom scripts to be used in pre and post
|
||||||
|
# run hooks.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# This is the instance of GUT that is running the tests. You can get
|
||||||
|
# information about the run from this object. This is set by GUT when the
|
||||||
|
# script is instantiated.
|
||||||
|
var gut = null
|
||||||
|
|
||||||
|
# the exit code to be used by gut_cmdln. See set method.
|
||||||
|
var _exit_code = null
|
||||||
|
|
||||||
|
var _should_abort = false
|
||||||
|
# Virtual method that will be called by GUT after instantiating
|
||||||
|
# this script.
|
||||||
|
func run():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Set the exit code when running from the command line. If not set then the
|
||||||
|
# default exit code will be returned (0 when no tests fail, 1 when any tests
|
||||||
|
# fail).
|
||||||
|
func set_exit_code(code):
|
||||||
|
_exit_code = code
|
||||||
|
|
||||||
|
func get_exit_code():
|
||||||
|
return _exit_code
|
||||||
|
|
||||||
|
# Usable by pre-run script to cause the run to end AFTER the run() method
|
||||||
|
# finishes. post-run script will not be ran.
|
||||||
|
func abort():
|
||||||
|
_should_abort = true
|
||||||
|
|
||||||
|
func should_abort():
|
||||||
|
return _should_abort
|
BIN
test_proj/addons/gut/icon.png
Normal file
BIN
test_proj/addons/gut/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 320 B |
34
test_proj/addons/gut/icon.png.import
Normal file
34
test_proj/addons/gut/icon.png.import
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="StreamTexture"
|
||||||
|
path="res://.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/gut/icon.png"
|
||||||
|
dest_files=[ "res://.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_mode=0
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
flags/repeat=0
|
||||||
|
flags/filter=true
|
||||||
|
flags/mipmaps=false
|
||||||
|
flags/anisotropic=false
|
||||||
|
flags/srgb=2
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/HDR_as_SRGB=false
|
||||||
|
process/invert_color=false
|
||||||
|
stream=false
|
||||||
|
size_limit=0
|
||||||
|
detect_3d=true
|
||||||
|
svg/scale=1.0
|
348
test_proj/addons/gut/logger.gd
Normal file
348
test_proj/addons/gut/logger.gd
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# This class wraps around the various printers and supplies formatting for the
|
||||||
|
# various message types (error, warning, etc).
|
||||||
|
# ##############################################################################
|
||||||
|
var types = {
|
||||||
|
debug = 'debug',
|
||||||
|
deprecated = 'deprecated',
|
||||||
|
error = 'error',
|
||||||
|
failed = 'failed',
|
||||||
|
info = 'info',
|
||||||
|
normal = 'normal',
|
||||||
|
orphan = 'orphan',
|
||||||
|
passed = 'passed',
|
||||||
|
pending = 'pending',
|
||||||
|
warn ='warn',
|
||||||
|
}
|
||||||
|
|
||||||
|
var fmts = {
|
||||||
|
red = 'red',
|
||||||
|
yellow = 'yellow',
|
||||||
|
green = 'green',
|
||||||
|
|
||||||
|
bold = 'bold',
|
||||||
|
underline = 'underline',
|
||||||
|
|
||||||
|
none = null
|
||||||
|
}
|
||||||
|
|
||||||
|
var _type_data = {
|
||||||
|
types.debug: {disp='DEBUG', enabled=true, fmt=fmts.none},
|
||||||
|
types.deprecated: {disp='DEPRECATED', enabled=true, fmt=fmts.none},
|
||||||
|
types.error: {disp='ERROR', enabled=true, fmt=fmts.red},
|
||||||
|
types.failed: {disp='Failed', enabled=true, fmt=fmts.red},
|
||||||
|
types.info: {disp='INFO', enabled=true, fmt=fmts.bold},
|
||||||
|
types.normal: {disp='NORMAL', enabled=true, fmt=fmts.none},
|
||||||
|
types.orphan: {disp='Orphans', enabled=true, fmt=fmts.yellow},
|
||||||
|
types.passed: {disp='Passed', enabled=true, fmt=fmts.green},
|
||||||
|
types.pending: {disp='Pending', enabled=true, fmt=fmts.yellow},
|
||||||
|
types.warn: {disp='WARNING', enabled=true, fmt=fmts.yellow},
|
||||||
|
}
|
||||||
|
|
||||||
|
var _logs = {
|
||||||
|
types.warn: [],
|
||||||
|
types.error: [],
|
||||||
|
types.info: [],
|
||||||
|
types.debug: [],
|
||||||
|
types.deprecated: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
var _printers = {
|
||||||
|
terminal = null,
|
||||||
|
gui = null,
|
||||||
|
console = null
|
||||||
|
}
|
||||||
|
|
||||||
|
var _gut = null
|
||||||
|
var _utils = null
|
||||||
|
var _indent_level = 0
|
||||||
|
var _indent_string = ' '
|
||||||
|
var _skip_test_name_for_testing = false
|
||||||
|
var _less_test_names = false
|
||||||
|
var _yield_calls = 0
|
||||||
|
var _last_yield_text = ''
|
||||||
|
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
_utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
_printers.terminal = _utils.Printers.TerminalPrinter.new()
|
||||||
|
_printers.console = _utils.Printers.ConsolePrinter.new()
|
||||||
|
# There were some problems in the timing of disabling this at the right
|
||||||
|
# time in gut_cmdln so it is disabled by default. This is enabled
|
||||||
|
# by plugin_control.gd based on settings.
|
||||||
|
_printers.console.set_disabled(true)
|
||||||
|
|
||||||
|
func get_indent_text():
|
||||||
|
var pad = ''
|
||||||
|
for i in range(_indent_level):
|
||||||
|
pad += _indent_string
|
||||||
|
|
||||||
|
return pad
|
||||||
|
|
||||||
|
func _indent_text(text):
|
||||||
|
var to_return = text
|
||||||
|
var ending_newline = ''
|
||||||
|
|
||||||
|
if(text.ends_with("\n")):
|
||||||
|
ending_newline = "\n"
|
||||||
|
to_return = to_return.left(to_return.length() -1)
|
||||||
|
|
||||||
|
var pad = get_indent_text()
|
||||||
|
to_return = to_return.replace("\n", "\n" + pad)
|
||||||
|
to_return += ending_newline
|
||||||
|
|
||||||
|
return pad + to_return
|
||||||
|
|
||||||
|
func _should_print_to_printer(key_name):
|
||||||
|
return _printers[key_name] != null and !_printers[key_name].get_disabled()
|
||||||
|
|
||||||
|
func _print_test_name():
|
||||||
|
if(_gut == null):
|
||||||
|
return
|
||||||
|
var cur_test = _gut.get_current_test_object()
|
||||||
|
if(cur_test == null):
|
||||||
|
return false
|
||||||
|
|
||||||
|
if(!cur_test.has_printed_name):
|
||||||
|
_output('* ' + cur_test.name + "\n")
|
||||||
|
cur_test.has_printed_name = true
|
||||||
|
|
||||||
|
func _output(text, fmt=null):
|
||||||
|
for key in _printers:
|
||||||
|
if(_should_print_to_printer(key)):
|
||||||
|
var info = ''#str(self, ':', key, ':', _printers[key], '| ')
|
||||||
|
_printers[key].send(info + text, fmt)
|
||||||
|
|
||||||
|
func _log(text, fmt=fmts.none):
|
||||||
|
_print_test_name()
|
||||||
|
var indented = _indent_text(text)
|
||||||
|
_output(indented, fmt)
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# Get Methods
|
||||||
|
# ---------------
|
||||||
|
func get_warnings():
|
||||||
|
return get_log_entries(types.warn)
|
||||||
|
|
||||||
|
func get_errors():
|
||||||
|
return get_log_entries(types.error)
|
||||||
|
|
||||||
|
func get_infos():
|
||||||
|
return get_log_entries(types.info)
|
||||||
|
|
||||||
|
func get_debugs():
|
||||||
|
return get_log_entries(types.debug)
|
||||||
|
|
||||||
|
func get_deprecated():
|
||||||
|
return get_log_entries(types.deprecated)
|
||||||
|
|
||||||
|
func get_count(log_type=null):
|
||||||
|
var count = 0
|
||||||
|
if(log_type == null):
|
||||||
|
for key in _logs:
|
||||||
|
count += _logs[key].size()
|
||||||
|
else:
|
||||||
|
count = _logs[log_type].size()
|
||||||
|
return count
|
||||||
|
|
||||||
|
func get_log_entries(log_type):
|
||||||
|
return _logs[log_type]
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# Log methods
|
||||||
|
# ---------------
|
||||||
|
func _output_type(type, text):
|
||||||
|
var td = _type_data[type]
|
||||||
|
if(!td.enabled):
|
||||||
|
return
|
||||||
|
|
||||||
|
_print_test_name()
|
||||||
|
if(type != types.normal):
|
||||||
|
if(_logs.has(type)):
|
||||||
|
_logs[type].append(text)
|
||||||
|
|
||||||
|
var start = str('[', td.disp, ']')
|
||||||
|
if(text != null and text != ''):
|
||||||
|
start += ': '
|
||||||
|
else:
|
||||||
|
start += ' '
|
||||||
|
var indented_start = _indent_text(start)
|
||||||
|
var indented_end = _indent_text(text)
|
||||||
|
indented_end = indented_end.lstrip(_indent_string)
|
||||||
|
_output(indented_start, td.fmt)
|
||||||
|
_output(indented_end + "\n")
|
||||||
|
|
||||||
|
func debug(text):
|
||||||
|
_output_type(types.debug, text)
|
||||||
|
|
||||||
|
# supply some text or the name of the deprecated method and the replacement.
|
||||||
|
func deprecated(text, alt_method=null):
|
||||||
|
var msg = text
|
||||||
|
if(alt_method):
|
||||||
|
msg = str('The method ', text, ' is deprecated, use ', alt_method , ' instead.')
|
||||||
|
return _output_type(types.deprecated, msg)
|
||||||
|
|
||||||
|
func error(text):
|
||||||
|
_output_type(types.error, text)
|
||||||
|
|
||||||
|
func failed(text):
|
||||||
|
_output_type(types.failed, text)
|
||||||
|
|
||||||
|
func info(text):
|
||||||
|
_output_type(types.info, text)
|
||||||
|
|
||||||
|
func orphan(text):
|
||||||
|
_output_type(types.orphan, text)
|
||||||
|
|
||||||
|
func passed(text):
|
||||||
|
_output_type(types.passed, text)
|
||||||
|
|
||||||
|
func pending(text):
|
||||||
|
_output_type(types.pending, text)
|
||||||
|
|
||||||
|
func warn(text):
|
||||||
|
_output_type(types.warn, text)
|
||||||
|
|
||||||
|
func log(text='', fmt=fmts.none):
|
||||||
|
end_yield()
|
||||||
|
if(text == ''):
|
||||||
|
_output("\n")
|
||||||
|
else:
|
||||||
|
_log(text + "\n", fmt)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func lograw(text, fmt=fmts.none):
|
||||||
|
return _output(text, fmt)
|
||||||
|
|
||||||
|
# Print the test name if we aren't skipping names of tests that pass (basically
|
||||||
|
# what _less_test_names means))
|
||||||
|
func log_test_name():
|
||||||
|
# suppress output if we haven't printed the test name yet and
|
||||||
|
# what to print is the test name.
|
||||||
|
if(!_less_test_names):
|
||||||
|
_print_test_name()
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# Misc
|
||||||
|
# ---------------
|
||||||
|
func get_gut():
|
||||||
|
return _gut
|
||||||
|
|
||||||
|
func set_gut(gut):
|
||||||
|
_gut = gut
|
||||||
|
if(_gut == null):
|
||||||
|
_printers.gui = null
|
||||||
|
else:
|
||||||
|
if(_printers.gui == null):
|
||||||
|
_printers.gui = _utils.Printers.GutGuiPrinter.new()
|
||||||
|
_printers.gui.set_gut(gut)
|
||||||
|
|
||||||
|
func get_indent_level():
|
||||||
|
return _indent_level
|
||||||
|
|
||||||
|
func set_indent_level(indent_level):
|
||||||
|
_indent_level = indent_level
|
||||||
|
|
||||||
|
func get_indent_string():
|
||||||
|
return _indent_string
|
||||||
|
|
||||||
|
func set_indent_string(indent_string):
|
||||||
|
_indent_string = indent_string
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
for key in _logs:
|
||||||
|
_logs[key].clear()
|
||||||
|
|
||||||
|
func inc_indent():
|
||||||
|
_indent_level += 1
|
||||||
|
|
||||||
|
func dec_indent():
|
||||||
|
_indent_level = max(0, _indent_level -1)
|
||||||
|
|
||||||
|
func is_type_enabled(type):
|
||||||
|
return _type_data[type].enabled
|
||||||
|
|
||||||
|
func set_type_enabled(type, is_enabled):
|
||||||
|
_type_data[type].enabled = is_enabled
|
||||||
|
|
||||||
|
func get_less_test_names():
|
||||||
|
return _less_test_names
|
||||||
|
|
||||||
|
func set_less_test_names(less_test_names):
|
||||||
|
_less_test_names = less_test_names
|
||||||
|
|
||||||
|
func disable_printer(name, is_disabled):
|
||||||
|
_printers[name].set_disabled(is_disabled)
|
||||||
|
|
||||||
|
func is_printer_disabled(name):
|
||||||
|
return _printers[name].get_disabled()
|
||||||
|
|
||||||
|
func disable_formatting(is_disabled):
|
||||||
|
for key in _printers:
|
||||||
|
_printers[key].set_format_enabled(!is_disabled)
|
||||||
|
|
||||||
|
func get_printer(printer_key):
|
||||||
|
return _printers[printer_key]
|
||||||
|
|
||||||
|
func _yield_text_terminal(text):
|
||||||
|
var printer = _printers['terminal']
|
||||||
|
if(_yield_calls != 0):
|
||||||
|
printer.clear_line()
|
||||||
|
printer.back(_last_yield_text.length())
|
||||||
|
printer.send(text, fmts.yellow)
|
||||||
|
|
||||||
|
func _end_yield_terminal():
|
||||||
|
var printer = _printers['terminal']
|
||||||
|
printer.clear_line()
|
||||||
|
printer.back(_last_yield_text.length())
|
||||||
|
|
||||||
|
func _yield_text_gui(text):
|
||||||
|
var lbl = _gut.get_gui().get_waiting_label()
|
||||||
|
lbl.visible = true
|
||||||
|
lbl.set_bbcode('[color=yellow]' + text + '[/color]')
|
||||||
|
|
||||||
|
func _end_yield_gui():
|
||||||
|
var lbl = _gut.get_gui().get_waiting_label()
|
||||||
|
lbl.visible = false
|
||||||
|
lbl.set_text('')
|
||||||
|
|
||||||
|
func yield_text(text):
|
||||||
|
_yield_text_terminal(text)
|
||||||
|
_yield_text_gui(text)
|
||||||
|
_last_yield_text = text
|
||||||
|
_yield_calls += 1
|
||||||
|
|
||||||
|
func end_yield():
|
||||||
|
if(_yield_calls == 0):
|
||||||
|
return
|
||||||
|
_end_yield_terminal()
|
||||||
|
_end_yield_gui()
|
||||||
|
_yield_calls = 0
|
||||||
|
_last_yield_text = ''
|
213
test_proj/addons/gut/method_maker.gd
Normal file
213
test_proj/addons/gut/method_maker.gd
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# This class will generate method declaration lines based on method meta
|
||||||
|
# data. It will create defaults that match the method data.
|
||||||
|
#
|
||||||
|
# --------------------
|
||||||
|
# function meta data
|
||||||
|
# --------------------
|
||||||
|
# name:
|
||||||
|
# flags:
|
||||||
|
# args: [{
|
||||||
|
# (class_name:),
|
||||||
|
# (hint:0),
|
||||||
|
# (hint_string:),
|
||||||
|
# (name:),
|
||||||
|
# (type:4),
|
||||||
|
# (usage:7)
|
||||||
|
# }]
|
||||||
|
# default_args []
|
||||||
|
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
var _lgr = _utils.get_logger()
|
||||||
|
const PARAM_PREFIX = 'p_'
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
# _supported_defaults
|
||||||
|
#
|
||||||
|
# This array contains all the data types that are supported for default values.
|
||||||
|
# If a value is supported it will contain either an empty string or a prefix
|
||||||
|
# that should be used when setting the parameter default value.
|
||||||
|
# For example int, real, bool do not need anything func(p1=1, p2=2.2, p3=false)
|
||||||
|
# but things like Vectors and Colors do since only the parameters to create a
|
||||||
|
# new Vector or Color are included in the metadata.
|
||||||
|
# ------------------------------------------------------
|
||||||
|
# TYPE_NIL = 0 — Variable is of type nil (only applied for null).
|
||||||
|
# TYPE_BOOL = 1 — Variable is of type bool.
|
||||||
|
# TYPE_INT = 2 — Variable is of type int.
|
||||||
|
# TYPE_REAL = 3 — Variable is of type float/real.
|
||||||
|
# TYPE_STRING = 4 — Variable is of type String.
|
||||||
|
# TYPE_VECTOR2 = 5 — Variable is of type Vector2.
|
||||||
|
# TYPE_RECT2 = 6 — Variable is of type Rect2.
|
||||||
|
# TYPE_VECTOR3 = 7 — Variable is of type Vector3.
|
||||||
|
# TYPE_COLOR = 14 — Variable is of type Color.
|
||||||
|
# TYPE_OBJECT = 17 — Variable is of type Object.
|
||||||
|
# TYPE_DICTIONARY = 18 — Variable is of type Dictionary.
|
||||||
|
# TYPE_ARRAY = 19 — Variable is of type Array.
|
||||||
|
# TYPE_VECTOR2_ARRAY = 24 — Variable is of type PoolVector2Array.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TYPE_TRANSFORM2D = 8 — Variable is of type Transform2D.
|
||||||
|
# TYPE_PLANE = 9 — Variable is of type Plane.
|
||||||
|
# TYPE_QUAT = 10 — Variable is of type Quat.
|
||||||
|
# TYPE_AABB = 11 — Variable is of type AABB.
|
||||||
|
# TYPE_BASIS = 12 — Variable is of type Basis.
|
||||||
|
# TYPE_TRANSFORM = 13 — Variable is of type Transform.
|
||||||
|
# TYPE_NODE_PATH = 15 — Variable is of type NodePath.
|
||||||
|
# TYPE_RID = 16 — Variable is of type RID.
|
||||||
|
# TYPE_RAW_ARRAY = 20 — Variable is of type PoolByteArray.
|
||||||
|
# TYPE_INT_ARRAY = 21 — Variable is of type PoolIntArray.
|
||||||
|
# TYPE_REAL_ARRAY = 22 — Variable is of type PoolRealArray.
|
||||||
|
# TYPE_STRING_ARRAY = 23 — Variable is of type PoolStringArray.
|
||||||
|
# TYPE_VECTOR3_ARRAY = 25 — Variable is of type PoolVector3Array.
|
||||||
|
# TYPE_COLOR_ARRAY = 26 — Variable is of type PoolColorArray.
|
||||||
|
# TYPE_MAX = 27 — Marker for end of type constants.
|
||||||
|
# ------------------------------------------------------
|
||||||
|
var _supported_defaults = []
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
for _i in range(TYPE_MAX):
|
||||||
|
_supported_defaults.append(null)
|
||||||
|
|
||||||
|
# These types do not require a prefix for defaults
|
||||||
|
_supported_defaults[TYPE_NIL] = ''
|
||||||
|
_supported_defaults[TYPE_BOOL] = ''
|
||||||
|
_supported_defaults[TYPE_INT] = ''
|
||||||
|
_supported_defaults[TYPE_REAL] = ''
|
||||||
|
_supported_defaults[TYPE_OBJECT] = ''
|
||||||
|
_supported_defaults[TYPE_ARRAY] = ''
|
||||||
|
_supported_defaults[TYPE_STRING] = ''
|
||||||
|
_supported_defaults[TYPE_DICTIONARY] = ''
|
||||||
|
_supported_defaults[TYPE_VECTOR2_ARRAY] = ''
|
||||||
|
|
||||||
|
# These require a prefix for whatever default is provided
|
||||||
|
_supported_defaults[TYPE_VECTOR2] = 'Vector2'
|
||||||
|
_supported_defaults[TYPE_RECT2] = 'Rect2'
|
||||||
|
_supported_defaults[TYPE_VECTOR3] = 'Vector3'
|
||||||
|
_supported_defaults[TYPE_COLOR] = 'Color'
|
||||||
|
|
||||||
|
# ###############
|
||||||
|
# Private
|
||||||
|
# ###############
|
||||||
|
var _func_text = _utils.get_file_as_text('res://addons/gut/double_templates/function_template.txt')
|
||||||
|
|
||||||
|
func _is_supported_default(type_flag):
|
||||||
|
return type_flag >= 0 and type_flag < _supported_defaults.size() and [type_flag] != null
|
||||||
|
|
||||||
|
# Creates a list of parameters with defaults of null unless a default value is
|
||||||
|
# found in the metadata. If a default is found in the meta then it is used if
|
||||||
|
# it is one we know how support.
|
||||||
|
#
|
||||||
|
# If a default is found that we don't know how to handle then this method will
|
||||||
|
# return null.
|
||||||
|
func _get_arg_text(method_meta):
|
||||||
|
var text = ''
|
||||||
|
var args = method_meta.args
|
||||||
|
var defaults = []
|
||||||
|
var has_unsupported_defaults = false
|
||||||
|
|
||||||
|
# fill up the defaults with null defaults for everything that doesn't have
|
||||||
|
# a default in the meta data. default_args is an array of default values
|
||||||
|
# for the last n parameters where n is the size of default_args so we only
|
||||||
|
# add nulls for everything up to the first parameter with a default.
|
||||||
|
for _i in range(args.size() - method_meta.default_args.size()):
|
||||||
|
defaults.append('null')
|
||||||
|
|
||||||
|
# Add meta-data defaults.
|
||||||
|
for i in range(method_meta.default_args.size()):
|
||||||
|
var t = args[defaults.size()]['type']
|
||||||
|
var value = ''
|
||||||
|
if(_is_supported_default(t)):
|
||||||
|
# strings are special, they need quotes around the value
|
||||||
|
if(t == TYPE_STRING):
|
||||||
|
value = str("'", str(method_meta.default_args[i]), "'")
|
||||||
|
# Colors need the parens but things like Vector2 and Rect2 don't
|
||||||
|
elif(t == TYPE_COLOR):
|
||||||
|
value = str(_supported_defaults[t], '(', str(method_meta.default_args[i]), ')')
|
||||||
|
elif(t == TYPE_OBJECT):
|
||||||
|
if(str(method_meta.default_args[i]) == "[Object:null]"):
|
||||||
|
value = str(_supported_defaults[t], 'null')
|
||||||
|
else:
|
||||||
|
value = str(_supported_defaults[t], str(method_meta.default_args[i]).to_lower())
|
||||||
|
|
||||||
|
# Everything else puts the prefix (if one is there) form _supported_defaults
|
||||||
|
# in front. The to_lower is used b/c for some reason the defaults for
|
||||||
|
# null, true, false are all "Null", "True", "False".
|
||||||
|
else:
|
||||||
|
value = str(_supported_defaults[t], str(method_meta.default_args[i]).to_lower())
|
||||||
|
else:
|
||||||
|
_lgr.warn(str(
|
||||||
|
'Unsupported default param type: ',method_meta.name, '-', args[defaults.size()].name, ' ', t, ' = ', method_meta.default_args[i]))
|
||||||
|
value = str('unsupported=',t)
|
||||||
|
has_unsupported_defaults = true
|
||||||
|
|
||||||
|
defaults.append(value)
|
||||||
|
|
||||||
|
# construct the string of parameters
|
||||||
|
for i in range(args.size()):
|
||||||
|
text += str(PARAM_PREFIX, args[i].name, '=', defaults[i])
|
||||||
|
if(i != args.size() -1):
|
||||||
|
text += ', '
|
||||||
|
|
||||||
|
# if we don't know how to make a default then we have to return null b/c
|
||||||
|
# it will cause a runtime error and it's one thing we could return to let
|
||||||
|
# callers know it didn't work.
|
||||||
|
if(has_unsupported_defaults):
|
||||||
|
text = null
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
# ###############
|
||||||
|
# Public
|
||||||
|
# ###############
|
||||||
|
|
||||||
|
# Creates a delceration for a function based off of function metadata. All
|
||||||
|
# types whose defaults are supported will have their values. If a datatype
|
||||||
|
# is not supported and the parameter has a default, a warning message will be
|
||||||
|
# printed and the declaration will return null.
|
||||||
|
func get_function_text(meta):
|
||||||
|
var method_params = _get_arg_text(meta)
|
||||||
|
var text = null
|
||||||
|
|
||||||
|
var param_array = get_spy_call_parameters_text(meta)
|
||||||
|
if(param_array == 'null'):
|
||||||
|
param_array = '[]'
|
||||||
|
|
||||||
|
if(method_params != null):
|
||||||
|
var decleration = str('func ', meta.name, '(', method_params, '):')
|
||||||
|
text = _func_text.format({
|
||||||
|
"func_decleration":decleration,
|
||||||
|
"method_name":meta.name,
|
||||||
|
"param_array":param_array,
|
||||||
|
"super_call":get_super_call_text(meta)
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
|
||||||
|
# creates a call to the function in meta in the super's class.
|
||||||
|
func get_super_call_text(meta):
|
||||||
|
var params = ''
|
||||||
|
|
||||||
|
for i in range(meta.args.size()):
|
||||||
|
params += PARAM_PREFIX + meta.args[i].name
|
||||||
|
if(meta.args.size() > 1 and i != meta.args.size() -1):
|
||||||
|
params += ', '
|
||||||
|
if(meta.name == '_init'):
|
||||||
|
return 'null'
|
||||||
|
else:
|
||||||
|
return str('.', meta.name, '(', params, ')')
|
||||||
|
|
||||||
|
func get_spy_call_parameters_text(meta):
|
||||||
|
var called_with = 'null'
|
||||||
|
if(meta.args.size() > 0):
|
||||||
|
called_with = '['
|
||||||
|
for i in range(meta.args.size()):
|
||||||
|
called_with += str(PARAM_PREFIX, meta.args[i].name)
|
||||||
|
if(i < meta.args.size() - 1):
|
||||||
|
called_with += ', '
|
||||||
|
called_with += ']'
|
||||||
|
return called_with
|
||||||
|
|
||||||
|
func get_logger():
|
||||||
|
return _lgr
|
||||||
|
|
||||||
|
func set_logger(logger):
|
||||||
|
_lgr = logger
|
38
test_proj/addons/gut/one_to_many.gd
Normal file
38
test_proj/addons/gut/one_to_many.gd
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# This datastructure represents a simple one-to-many relationship. It manages
|
||||||
|
# a dictionary of value/array pairs. It ignores duplicates of both the "one"
|
||||||
|
# and the "many".
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
var _items = {}
|
||||||
|
|
||||||
|
# return the size of _items or the size of an element in _items if "one" was
|
||||||
|
# specified.
|
||||||
|
func size(one=null):
|
||||||
|
var to_return = 0
|
||||||
|
if(one == null):
|
||||||
|
to_return = _items.size()
|
||||||
|
elif(_items.has(one)):
|
||||||
|
to_return = _items[one].size()
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# Add an element to "one" if it does not already exist
|
||||||
|
func add(one, many_item):
|
||||||
|
if(_items.has(one) and !_items[one].has(many_item)):
|
||||||
|
_items[one].append(many_item)
|
||||||
|
else:
|
||||||
|
_items[one] = [many_item]
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
_items.clear()
|
||||||
|
|
||||||
|
func has(one, many_item):
|
||||||
|
var to_return = false
|
||||||
|
if(_items.has(one)):
|
||||||
|
to_return = _items[one].has(many_item)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var to_return = ''
|
||||||
|
for key in _items:
|
||||||
|
to_return += str(key, ": ", _items[key], "\n")
|
||||||
|
return to_return
|
248
test_proj/addons/gut/optparse.gd
Normal file
248
test_proj/addons/gut/optparse.gd
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# Description
|
||||||
|
# -----------
|
||||||
|
# Command line interface for the GUT unit testing tool. Allows you to run tests
|
||||||
|
# from the command line instead of running a scene. Place this script along with
|
||||||
|
# gut.gd into your scripts directory at the root of your project. Once there you
|
||||||
|
# can run this script (from the root of your project) using the following command:
|
||||||
|
# godot -s -d test/gut/gut_cmdln.gd
|
||||||
|
#
|
||||||
|
# See the readme for a list of options and examples. You can also use the -gh
|
||||||
|
# option to get more information about how to use the command line interface.
|
||||||
|
# ##############################################################################
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# Parses the command line arguments supplied into an array that can then be
|
||||||
|
# examined and parsed based on how the gut options work.
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
class CmdLineParser:
|
||||||
|
var _used_options = []
|
||||||
|
# an array of arrays. Each element in this array will contain an option
|
||||||
|
# name and if that option contains a value then it will have a sedond
|
||||||
|
# element. For example:
|
||||||
|
# [[-gselect, test.gd], [-gexit]]
|
||||||
|
var _opts = []
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
for i in range(OS.get_cmdline_args().size()):
|
||||||
|
var opt_val = OS.get_cmdline_args()[i].split('=')
|
||||||
|
_opts.append(opt_val)
|
||||||
|
|
||||||
|
# Parse out multiple comma delimited values from a command line
|
||||||
|
# option. Values are separated from option name with "=" and
|
||||||
|
# additional values are comma separated.
|
||||||
|
func _parse_array_value(full_option):
|
||||||
|
var value = _parse_option_value(full_option)
|
||||||
|
var split = value.split(',')
|
||||||
|
return split
|
||||||
|
|
||||||
|
# Parse out the value of an option. Values are separated from
|
||||||
|
# the option name with "="
|
||||||
|
func _parse_option_value(full_option):
|
||||||
|
if(full_option.size() > 1):
|
||||||
|
return full_option[1]
|
||||||
|
else:
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Search _opts for an element that starts with the option name
|
||||||
|
# specified.
|
||||||
|
func find_option(name):
|
||||||
|
var found = false
|
||||||
|
var idx = 0
|
||||||
|
|
||||||
|
while(idx < _opts.size() and !found):
|
||||||
|
if(_opts[idx][0] == name):
|
||||||
|
found = true
|
||||||
|
else:
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
if(found):
|
||||||
|
return idx
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
func get_array_value(option):
|
||||||
|
_used_options.append(option)
|
||||||
|
var to_return = []
|
||||||
|
var opt_loc = find_option(option)
|
||||||
|
if(opt_loc != -1):
|
||||||
|
to_return = _parse_array_value(_opts[opt_loc])
|
||||||
|
_opts.remove(opt_loc)
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# returns the value of an option if it was specified, null otherwise. This
|
||||||
|
# used to return the default but that became problemnatic when trying to
|
||||||
|
# punch through the different places where values could be specified.
|
||||||
|
func get_value(option):
|
||||||
|
_used_options.append(option)
|
||||||
|
var to_return = null
|
||||||
|
var opt_loc = find_option(option)
|
||||||
|
if(opt_loc != -1):
|
||||||
|
to_return = _parse_option_value(_opts[opt_loc])
|
||||||
|
_opts.remove(opt_loc)
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# returns true if it finds the option, false if not.
|
||||||
|
func was_specified(option):
|
||||||
|
_used_options.append(option)
|
||||||
|
return find_option(option) != -1
|
||||||
|
|
||||||
|
# Returns any unused command line options. I found that only the -s and
|
||||||
|
# script name come through from godot, all other options that godot uses
|
||||||
|
# are not sent through OS.get_cmdline_args().
|
||||||
|
#
|
||||||
|
# This is a onetime thing b/c i kill all items in _used_options
|
||||||
|
func get_unused_options():
|
||||||
|
var to_return = []
|
||||||
|
for i in range(_opts.size()):
|
||||||
|
to_return.append(_opts[i][0])
|
||||||
|
|
||||||
|
var script_option = to_return.find('-s')
|
||||||
|
if script_option != -1:
|
||||||
|
to_return.remove(script_option + 1)
|
||||||
|
to_return.remove(script_option)
|
||||||
|
|
||||||
|
while(_used_options.size() > 0):
|
||||||
|
var index = to_return.find(_used_options[0].split("=")[0])
|
||||||
|
if(index != -1):
|
||||||
|
to_return.remove(index)
|
||||||
|
_used_options.remove(0)
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# Simple class to hold a command line option
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
class Option:
|
||||||
|
var value = null
|
||||||
|
var option_name = ''
|
||||||
|
var default = null
|
||||||
|
var description = ''
|
||||||
|
|
||||||
|
func _init(name, default_value, desc=''):
|
||||||
|
option_name = name
|
||||||
|
default = default_value
|
||||||
|
description = desc
|
||||||
|
value = null#default_value
|
||||||
|
|
||||||
|
func pad(to_pad, size, pad_with=' '):
|
||||||
|
var to_return = to_pad
|
||||||
|
for _i in range(to_pad.length(), size):
|
||||||
|
to_return += pad_with
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func to_s(min_space=0):
|
||||||
|
var subbed_desc = description
|
||||||
|
if(subbed_desc.find('[default]') != -1):
|
||||||
|
subbed_desc = subbed_desc.replace('[default]', str(default))
|
||||||
|
return pad(option_name, min_space) + subbed_desc
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# The high level interface between this script and the command line options
|
||||||
|
# supplied. Uses Option class and CmdLineParser to extract information from
|
||||||
|
# the command line and make it easily accessible.
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
var options = []
|
||||||
|
var _opts = []
|
||||||
|
var _banner = ''
|
||||||
|
|
||||||
|
func add(name, default, desc):
|
||||||
|
options.append(Option.new(name, default, desc))
|
||||||
|
|
||||||
|
func get_value(name):
|
||||||
|
var found = false
|
||||||
|
var idx = 0
|
||||||
|
|
||||||
|
while(idx < options.size() and !found):
|
||||||
|
if(options[idx].option_name == name):
|
||||||
|
found = true
|
||||||
|
else:
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
if(found):
|
||||||
|
return options[idx].value
|
||||||
|
else:
|
||||||
|
print("COULD NOT FIND OPTION " + name)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func set_banner(banner):
|
||||||
|
_banner = banner
|
||||||
|
|
||||||
|
func print_help():
|
||||||
|
var longest = 0
|
||||||
|
for i in range(options.size()):
|
||||||
|
if(options[i].option_name.length() > longest):
|
||||||
|
longest = options[i].option_name.length()
|
||||||
|
|
||||||
|
print('---------------------------------------------------------')
|
||||||
|
print(_banner)
|
||||||
|
|
||||||
|
print("\nOptions\n-------")
|
||||||
|
for i in range(options.size()):
|
||||||
|
print(' ' + options[i].to_s(longest + 2))
|
||||||
|
print('---------------------------------------------------------')
|
||||||
|
|
||||||
|
func print_options():
|
||||||
|
for i in range(options.size()):
|
||||||
|
print(options[i].option_name + '=' + str(options[i].value))
|
||||||
|
|
||||||
|
func parse():
|
||||||
|
var parser = CmdLineParser.new()
|
||||||
|
|
||||||
|
for i in range(options.size()):
|
||||||
|
var t = typeof(options[i].default)
|
||||||
|
# only set values that were specified at the command line so that
|
||||||
|
# we can punch through default and config values correctly later.
|
||||||
|
# Without this check, you can't tell the difference between the
|
||||||
|
# defaults and what was specified, so you can't punch through
|
||||||
|
# higher level options.
|
||||||
|
if(parser.was_specified(options[i].option_name)):
|
||||||
|
if(t == TYPE_INT):
|
||||||
|
options[i].value = int(parser.get_value(options[i].option_name))
|
||||||
|
elif(t == TYPE_STRING):
|
||||||
|
options[i].value = parser.get_value(options[i].option_name)
|
||||||
|
elif(t == TYPE_ARRAY):
|
||||||
|
options[i].value = parser.get_array_value(options[i].option_name)
|
||||||
|
elif(t == TYPE_BOOL):
|
||||||
|
options[i].value = parser.was_specified(options[i].option_name)
|
||||||
|
elif(t == TYPE_NIL):
|
||||||
|
print(options[i].option_name + ' cannot be processed, it has a nil datatype')
|
||||||
|
else:
|
||||||
|
print(options[i].option_name + ' cannot be processed, it has unknown datatype:' + str(t))
|
||||||
|
|
||||||
|
var unused = parser.get_unused_options()
|
||||||
|
if(unused.size() > 0):
|
||||||
|
print("Unrecognized options: ", unused)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
55
test_proj/addons/gut/orphan_counter.gd
Normal file
55
test_proj/addons/gut/orphan_counter.gd
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# This is a utility for tracking changes in the orphan count. Each time
|
||||||
|
# add_counter is called it adds/resets the value in the dictionary to the
|
||||||
|
# current number of orphans. Each call to get_counter will return the change
|
||||||
|
# in orphans since add_counter was last called.
|
||||||
|
# ##############################################################################
|
||||||
|
var _counters = {}
|
||||||
|
|
||||||
|
func orphan_count():
|
||||||
|
return Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT)
|
||||||
|
|
||||||
|
func add_counter(name):
|
||||||
|
_counters[name] = orphan_count()
|
||||||
|
|
||||||
|
# Returns the number of orphans created since add_counter was last called for
|
||||||
|
# the name. Returns -1 to avoid blowing up with an invalid name but still
|
||||||
|
# be somewhat visible that we've done something wrong.
|
||||||
|
func get_counter(name):
|
||||||
|
return orphan_count() - _counters[name] if _counters.has(name) else -1
|
||||||
|
|
||||||
|
func print_orphans(name, lgr):
|
||||||
|
var count = get_counter(name)
|
||||||
|
|
||||||
|
if(count > 0):
|
||||||
|
var o = 'orphan'
|
||||||
|
if(count > 1):
|
||||||
|
o = 'orphans'
|
||||||
|
lgr.orphan(str(count, ' new ', o, '(', name, ').'))
|
75
test_proj/addons/gut/parameter_factory.gd
Normal file
75
test_proj/addons/gut/parameter_factory.gd
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# This is the home for all parameter creation helpers. These functions should
|
||||||
|
# all return an array of values to be used as parameters for parameterized
|
||||||
|
# tests.
|
||||||
|
# ##############################################################################
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Creates an array of dictionaries. It pairs up the names array with each set
|
||||||
|
# of values in values. If more names than values are specified then the missing
|
||||||
|
# values will be filled with nulls. If more values than names are specified
|
||||||
|
# those values will be ignored.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# create_named_parameters(['a', 'b'], [[1, 2], ['one', 'two']]) returns
|
||||||
|
# [{a:1, b:2}, {a:'one', b:'two'}]
|
||||||
|
#
|
||||||
|
# This allows you to increase readability of your parameterized tests:
|
||||||
|
# var params = create_named_parameters(['a', 'b'], [[1, 2], ['one', 'two']])
|
||||||
|
# func test_foo(p = use_parameters(params)):
|
||||||
|
# assert_eq(p.a, p.b)
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
# names: an array of names to be used as keys in the dictionaries
|
||||||
|
# values: an array of arrays of values.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
static func named_parameters(names, values):
|
||||||
|
var named = []
|
||||||
|
for i in range(values.size()):
|
||||||
|
var entry = {}
|
||||||
|
|
||||||
|
var parray = values[i]
|
||||||
|
if(typeof(parray) != TYPE_ARRAY):
|
||||||
|
parray = [values[i]]
|
||||||
|
|
||||||
|
for j in range(names.size()):
|
||||||
|
if(j >= parray.size()):
|
||||||
|
entry[names[j]] = null
|
||||||
|
else:
|
||||||
|
entry[names[j]] = parray[j]
|
||||||
|
named.append(entry)
|
||||||
|
|
||||||
|
return named
|
||||||
|
|
||||||
|
# Additional Helper Ideas
|
||||||
|
# * File. IDK what it would look like. csv maybe.
|
||||||
|
# * Random values within a range?
|
||||||
|
# * All int values in a range or add an optioanal step.
|
||||||
|
# *
|
37
test_proj/addons/gut/parameter_handler.gd
Normal file
37
test_proj/addons/gut/parameter_handler.gd
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
var _params = null
|
||||||
|
var _call_count = 0
|
||||||
|
var _logger = null
|
||||||
|
|
||||||
|
func _init(params=null):
|
||||||
|
_params = params
|
||||||
|
_logger = _utils.get_logger()
|
||||||
|
if(typeof(_params) != TYPE_ARRAY):
|
||||||
|
_logger.error('You must pass an array to parameter_handler constructor.')
|
||||||
|
_params = null
|
||||||
|
|
||||||
|
|
||||||
|
func next_parameters():
|
||||||
|
_call_count += 1
|
||||||
|
return _params[_call_count -1]
|
||||||
|
|
||||||
|
func get_current_parameters():
|
||||||
|
return _params[_call_count]
|
||||||
|
|
||||||
|
func is_done():
|
||||||
|
var done = true
|
||||||
|
if(_params != null):
|
||||||
|
done = _call_count == _params.size()
|
||||||
|
return done
|
||||||
|
|
||||||
|
func get_logger():
|
||||||
|
return _logger
|
||||||
|
|
||||||
|
func set_logger(logger):
|
||||||
|
_logger = logger
|
||||||
|
|
||||||
|
func get_call_count():
|
||||||
|
return _call_count
|
||||||
|
|
||||||
|
func get_parameter_count():
|
||||||
|
return _params.size()
|
7
test_proj/addons/gut/plugin.cfg
Normal file
7
test_proj/addons/gut/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Gut"
|
||||||
|
description="Unit Testing tool for Godot."
|
||||||
|
author="Butch Wesley"
|
||||||
|
version="7.0.0"
|
||||||
|
script="gut_plugin.gd"
|
247
test_proj/addons/gut/plugin_control.gd
Normal file
247
test_proj/addons/gut/plugin_control.gd
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# This is the control that is added via the editor. It exposes GUT settings
|
||||||
|
# through the editor and delays the creation of the GUT instance until
|
||||||
|
# Engine.get_main_loop() works as expected.
|
||||||
|
# ##############################################################################
|
||||||
|
tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# GUT Settings
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
export(String, 'AnonymousPro', 'CourierPrime', 'LobsterTwo', 'Default') var _font_name = 'AnonymousPro'
|
||||||
|
export(int) var _font_size = 20
|
||||||
|
export(Color) var _font_color = Color(.8, .8, .8, 1)
|
||||||
|
export(Color) var _background_color = Color(.15, .15, .15, 1)
|
||||||
|
# Enable/Disable coloring of output.
|
||||||
|
export(bool) var _color_output = true
|
||||||
|
# The full/partial name of a script to select upon startup
|
||||||
|
export(String) var _select_script = ''
|
||||||
|
# The full/partial name of a test. All tests that contain the string will be
|
||||||
|
# run
|
||||||
|
export(String) var _tests_like = ''
|
||||||
|
# The full/partial name of an Inner Class to be run. All Inner Classes that
|
||||||
|
# contain the string will be run.
|
||||||
|
export(String) var _inner_class_name = ''
|
||||||
|
# Start running tests when the scene finishes loading
|
||||||
|
export var _run_on_load = false
|
||||||
|
# Maximize the GUT control on startup
|
||||||
|
export var _should_maximize = false
|
||||||
|
# Print output to the consol as well
|
||||||
|
export var _should_print_to_console = true
|
||||||
|
# Display orphan counts at the end of tests/scripts.
|
||||||
|
export var _show_orphans = true
|
||||||
|
# The log level.
|
||||||
|
export(int, 'Fail/Errors', 'Errors/Warnings/Test Names', 'Everything') var _log_level = 1
|
||||||
|
# When enabled GUT will yield between tests to give the GUI time to paint.
|
||||||
|
# Disabling this can make the program appear to hang and can have some
|
||||||
|
# unwanted consequences with the timing of freeing objects
|
||||||
|
export var _yield_between_tests = true
|
||||||
|
# When GUT compares values it first checks the types to prevent runtime errors.
|
||||||
|
# This behavior can be disabled if desired. This flag was added early in
|
||||||
|
# development to prevent any breaking changes and will likely be removed in
|
||||||
|
# the future.
|
||||||
|
export var _disable_strict_datatype_checks = false
|
||||||
|
# The prefix used to find test methods.
|
||||||
|
export var _test_prefix = 'test_'
|
||||||
|
# The prefix used to find test scripts.
|
||||||
|
export var _file_prefix = 'test_'
|
||||||
|
# The file extension for test scripts (I don't think you can change this and
|
||||||
|
# everythign work).
|
||||||
|
export var _file_extension = '.gd'
|
||||||
|
# The prefix used to find Inner Test Classes.
|
||||||
|
export var _inner_class_prefix = 'Test'
|
||||||
|
# The directory GUT will use to write any temporary files. This isn't used
|
||||||
|
# much anymore since there was a change to the double creation implementation.
|
||||||
|
# This will be removed in a later release.
|
||||||
|
export(String) var _temp_directory = 'user://gut_temp_directory'
|
||||||
|
# The path and filename for exported test information.
|
||||||
|
export(String) var _export_path = ''
|
||||||
|
# When enabled, any directory added will also include its subdirectories when
|
||||||
|
# GUT looks for test scripts.
|
||||||
|
export var _include_subdirectories = false
|
||||||
|
# Allow user to add test directories via editor. This is done with strings
|
||||||
|
# instead of an array because the interface for editing arrays is really
|
||||||
|
# cumbersome and complicates testing because arrays set through the editor
|
||||||
|
# apply to ALL instances. This also allows the user to use the built in
|
||||||
|
# dialog to pick a directory.
|
||||||
|
export(String, DIR) var _directory1 = ''
|
||||||
|
export(String, DIR) var _directory2 = ''
|
||||||
|
export(String, DIR) var _directory3 = ''
|
||||||
|
export(String, DIR) var _directory4 = ''
|
||||||
|
export(String, DIR) var _directory5 = ''
|
||||||
|
export(String, DIR) var _directory6 = ''
|
||||||
|
# Must match the types in _utils for double strategy
|
||||||
|
export(int, 'FULL', 'PARTIAL') var _double_strategy = 1
|
||||||
|
# Path and filename to the script to run before all tests are run.
|
||||||
|
export(String, FILE) var _pre_run_script = ''
|
||||||
|
# Path and filename to the script to run after all tests are run.
|
||||||
|
export(String, FILE) var _post_run_script = ''
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Signals
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Emitted when all the tests have finished running.
|
||||||
|
signal tests_finished
|
||||||
|
# Emitted when GUT is ready to be interacted with, and before any tests are run.
|
||||||
|
signal gut_ready
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private stuff.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
var _gut = null
|
||||||
|
var _lgr = null
|
||||||
|
var _cancel_import = false
|
||||||
|
var _placeholder = null
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
# This min size has to be what the min size of the GutScene's min size is
|
||||||
|
# but it has to be set here and not inferred i think.
|
||||||
|
rect_min_size = Vector2(740, 250)
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# Must call this deferred so that there is enough time for
|
||||||
|
# Engine.get_main_loop() is populated and the psuedo singleton utils.gd
|
||||||
|
# can be setup correctly.
|
||||||
|
if(Engine.editor_hint):
|
||||||
|
_placeholder = load('res://addons/gut/GutScene.tscn').instance()
|
||||||
|
call_deferred('add_child', _placeholder)
|
||||||
|
_placeholder.rect_size = rect_size
|
||||||
|
else:
|
||||||
|
call_deferred('_setup_gut')
|
||||||
|
|
||||||
|
connect('resized', self, '_on_resized')
|
||||||
|
|
||||||
|
func _on_resized():
|
||||||
|
if(_placeholder != null):
|
||||||
|
_placeholder.rect_size = rect_size
|
||||||
|
|
||||||
|
|
||||||
|
# Templates can be missing if tests are exported and the export config for the
|
||||||
|
# project does not include '*.txt' files. This check and related flags make
|
||||||
|
# sure GUT does not blow up and that the error is not lost in all the import
|
||||||
|
# output that is generated as well as ensuring that no tests are run.
|
||||||
|
#
|
||||||
|
# Assumption: This is only a concern when running from the scene since you
|
||||||
|
# cannot run GUT from the command line in an exported game.
|
||||||
|
func _check_for_templates():
|
||||||
|
var f = File.new()
|
||||||
|
if(!f.file_exists('res://addons/gut/double_templates/function_template.txt')):
|
||||||
|
_lgr.error('Templates are missing. Make sure you are exporting "*.txt" or "addons/gut/double_templates/*.txt".')
|
||||||
|
_run_on_load = false
|
||||||
|
_cancel_import = true
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _setup_gut():
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
|
||||||
|
_lgr = _utils.get_logger()
|
||||||
|
_gut = load('res://addons/gut/gut.gd').new()
|
||||||
|
_gut.connect('tests_finished', self, '_on_tests_finished')
|
||||||
|
|
||||||
|
if(!_check_for_templates()):
|
||||||
|
return
|
||||||
|
|
||||||
|
_gut._select_script = _select_script
|
||||||
|
_gut._tests_like = _tests_like
|
||||||
|
_gut._inner_class_name = _inner_class_name
|
||||||
|
|
||||||
|
_gut._test_prefix = _test_prefix
|
||||||
|
_gut._file_prefix = _file_prefix
|
||||||
|
_gut._file_extension = _file_extension
|
||||||
|
_gut._inner_class_prefix = _inner_class_prefix
|
||||||
|
_gut._temp_directory = _temp_directory
|
||||||
|
|
||||||
|
_gut.set_should_maximize(_should_maximize)
|
||||||
|
_gut.set_yield_between_tests(_yield_between_tests)
|
||||||
|
_gut.disable_strict_datatype_checks(_disable_strict_datatype_checks)
|
||||||
|
_gut.set_export_path(_export_path)
|
||||||
|
_gut.set_include_subdirectories(_include_subdirectories)
|
||||||
|
_gut.set_double_strategy(_double_strategy)
|
||||||
|
_gut.set_pre_run_script(_pre_run_script)
|
||||||
|
_gut.set_post_run_script(_post_run_script)
|
||||||
|
_gut.set_color_output(_color_output)
|
||||||
|
_gut.show_orphans(_show_orphans)
|
||||||
|
|
||||||
|
get_parent().add_child(_gut)
|
||||||
|
|
||||||
|
if(!_utils.is_version_ok()):
|
||||||
|
return
|
||||||
|
|
||||||
|
_gut.set_log_level(_log_level)
|
||||||
|
|
||||||
|
_gut.add_directory(_directory1)
|
||||||
|
_gut.add_directory(_directory2)
|
||||||
|
_gut.add_directory(_directory3)
|
||||||
|
_gut.add_directory(_directory4)
|
||||||
|
_gut.add_directory(_directory5)
|
||||||
|
_gut.add_directory(_directory6)
|
||||||
|
|
||||||
|
_gut.get_logger().disable_printer('console', !_should_print_to_console)
|
||||||
|
# When file logging enabled then the log will contain terminal escape
|
||||||
|
# strings. So when running the scene this is disabled. Also if enabled
|
||||||
|
# this may cause duplicate entries into the logs.
|
||||||
|
_gut.get_logger().disable_printer('terminal', true)
|
||||||
|
|
||||||
|
_gut.get_gui().set_font_size(_font_size)
|
||||||
|
_gut.get_gui().set_font(_font_name)
|
||||||
|
_gut.get_gui().set_default_font_color(_font_color)
|
||||||
|
_gut.get_gui().set_background_color(_background_color)
|
||||||
|
_gut.get_gui().rect_size = rect_size
|
||||||
|
emit_signal('gut_ready')
|
||||||
|
|
||||||
|
if(_run_on_load):
|
||||||
|
# Run the test scripts. If one has been selected then only run that one
|
||||||
|
# otherwise all tests will be run.
|
||||||
|
var run_rest_of_scripts = _select_script == null
|
||||||
|
_gut.test_scripts(run_rest_of_scripts)
|
||||||
|
|
||||||
|
func _is_ready_to_go(action):
|
||||||
|
if(_gut == null):
|
||||||
|
push_error(str('GUT is not ready for ', action, ' yet. Perform actions on GUT in/after the gut_ready signal.'))
|
||||||
|
return _gut != null
|
||||||
|
|
||||||
|
func _on_tests_finished():
|
||||||
|
emit_signal('tests_finished')
|
||||||
|
|
||||||
|
func get_gut():
|
||||||
|
return _gut
|
||||||
|
|
||||||
|
func export_if_tests_found():
|
||||||
|
if(_is_ready_to_go('export_if_tests_found')):
|
||||||
|
_gut.export_if_tests_found()
|
||||||
|
|
||||||
|
func import_tests_if_none_found():
|
||||||
|
if(_is_ready_to_go('import_tests_if_none_found') and !_cancel_import):
|
||||||
|
_gut.import_tests_if_none_found()
|
157
test_proj/addons/gut/printers.gd
Normal file
157
test_proj/addons/gut/printers.gd
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Interface and some basic functionality for all printers.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class Printer:
|
||||||
|
var _format_enabled = true
|
||||||
|
var _disabled = false
|
||||||
|
var _printer_name = 'NOT SET'
|
||||||
|
var _show_name = false # used for debugging, set manually
|
||||||
|
|
||||||
|
func get_format_enabled():
|
||||||
|
return _format_enabled
|
||||||
|
|
||||||
|
func set_format_enabled(format_enabled):
|
||||||
|
_format_enabled = format_enabled
|
||||||
|
|
||||||
|
func send(text, fmt=null):
|
||||||
|
if(_disabled):
|
||||||
|
return
|
||||||
|
|
||||||
|
var formatted = text
|
||||||
|
if(fmt != null and _format_enabled):
|
||||||
|
formatted = format_text(text, fmt)
|
||||||
|
|
||||||
|
if(_show_name):
|
||||||
|
formatted = str('(', _printer_name, ')') + formatted
|
||||||
|
|
||||||
|
_output(formatted)
|
||||||
|
|
||||||
|
func get_disabled():
|
||||||
|
return _disabled
|
||||||
|
|
||||||
|
func set_disabled(disabled):
|
||||||
|
_disabled = disabled
|
||||||
|
|
||||||
|
# --------------------
|
||||||
|
# Virtual Methods (some have some default behavior)
|
||||||
|
# --------------------
|
||||||
|
func _output(text):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func format_text(text, fmt):
|
||||||
|
return text
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Responsible for sending text to a GUT gui.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class GutGuiPrinter:
|
||||||
|
extends Printer
|
||||||
|
var _gut = null
|
||||||
|
|
||||||
|
var _colors = {
|
||||||
|
red = Color.red,
|
||||||
|
yellow = Color.yellow,
|
||||||
|
green = Color.green
|
||||||
|
}
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
_printer_name = 'gui'
|
||||||
|
|
||||||
|
func _wrap_with_tag(text, tag):
|
||||||
|
return str('[', tag, ']', text, '[/', tag, ']')
|
||||||
|
|
||||||
|
func _color_text(text, c_word):
|
||||||
|
return '[color=' + c_word + ']' + text + '[/color]'
|
||||||
|
|
||||||
|
func format_text(text, fmt):
|
||||||
|
var box = _gut.get_gui().get_text_box()
|
||||||
|
|
||||||
|
if(fmt == 'bold'):
|
||||||
|
box.push_bold()
|
||||||
|
elif(fmt == 'underline'):
|
||||||
|
box.push_underline()
|
||||||
|
elif(_colors.has(fmt)):
|
||||||
|
box.push_color(_colors[fmt])
|
||||||
|
else:
|
||||||
|
# just pushing something to pop.
|
||||||
|
box.push_normal()
|
||||||
|
|
||||||
|
box.add_text(text)
|
||||||
|
box.pop()
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
func _output(text):
|
||||||
|
_gut.get_gui().get_text_box().add_text(text)
|
||||||
|
|
||||||
|
func get_gut():
|
||||||
|
return _gut
|
||||||
|
|
||||||
|
func set_gut(gut):
|
||||||
|
_gut = gut
|
||||||
|
|
||||||
|
# This can be very very slow when the box has a lot of text.
|
||||||
|
func clear_line():
|
||||||
|
var box = _gut.get_gui().get_text_box()
|
||||||
|
box.remove_line(box.get_line_count() - 1)
|
||||||
|
box.update()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# This AND TerminalPrinter should not be enabled at the same time since it will
|
||||||
|
# result in duplicate output. printraw does not print to the console so i had
|
||||||
|
# to make another one.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class ConsolePrinter:
|
||||||
|
extends Printer
|
||||||
|
var _buffer = ''
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
_printer_name = 'console'
|
||||||
|
|
||||||
|
# suppresses output until it encounters a newline to keep things
|
||||||
|
# inline as much as possible.
|
||||||
|
func _output(text):
|
||||||
|
if(text.ends_with("\n")):
|
||||||
|
print(_buffer + text.left(text.length() -1))
|
||||||
|
_buffer = ''
|
||||||
|
else:
|
||||||
|
_buffer += text
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Prints text to terminal, formats some words.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class TerminalPrinter:
|
||||||
|
extends Printer
|
||||||
|
|
||||||
|
var escape = PoolByteArray([0x1b]).get_string_from_ascii()
|
||||||
|
var cmd_colors = {
|
||||||
|
red = escape + '[31m',
|
||||||
|
yellow = escape + '[33m',
|
||||||
|
green = escape + '[32m',
|
||||||
|
|
||||||
|
underline = escape + '[4m',
|
||||||
|
bold = escape + '[1m',
|
||||||
|
|
||||||
|
default = escape + '[0m',
|
||||||
|
|
||||||
|
clear_line = escape + '[2K'
|
||||||
|
}
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
_printer_name = 'terminal'
|
||||||
|
|
||||||
|
func _output(text):
|
||||||
|
# Note, printraw does not print to the console.
|
||||||
|
printraw(text)
|
||||||
|
|
||||||
|
func format_text(text, fmt):
|
||||||
|
return cmd_colors[fmt] + text + cmd_colors.default
|
||||||
|
|
||||||
|
func clear_line():
|
||||||
|
send(cmd_colors.clear_line)
|
||||||
|
|
||||||
|
func back(n):
|
||||||
|
send(escape + str('[', n, 'D'))
|
||||||
|
|
||||||
|
func forward(n):
|
||||||
|
send(escape + str('[', n, 'C'))
|
166
test_proj/addons/gut/signal_watcher.gd
Normal file
166
test_proj/addons/gut/signal_watcher.gd
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
|
||||||
|
# Some arbitrary string that should never show up by accident. If it does, then
|
||||||
|
# shame on you.
|
||||||
|
const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'
|
||||||
|
|
||||||
|
# This hash holds the objects that are being watched, the signals that are being
|
||||||
|
# watched, and an array of arrays that contains arguments that were passed
|
||||||
|
# each time the signal was emitted.
|
||||||
|
#
|
||||||
|
# For example:
|
||||||
|
# _watched_signals => {
|
||||||
|
# ref1 => {
|
||||||
|
# 'signal1' => [[], [], []],
|
||||||
|
# 'signal2' => [[p1, p2]],
|
||||||
|
# 'signal3' => [[p1]]
|
||||||
|
# },
|
||||||
|
# ref2 => {
|
||||||
|
# 'some_signal' => [],
|
||||||
|
# 'other_signal' => [[p1, p2, p3], [p1, p2, p3], [p1, p2, p3]]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# In this sample:
|
||||||
|
# - signal1 on the ref1 object was emitted 3 times and each time, zero
|
||||||
|
# parameters were passed.
|
||||||
|
# - signal3 on ref1 was emitted once and passed a single parameter
|
||||||
|
# - some_signal on ref2 was never emitted.
|
||||||
|
# - other_signal on ref2 was emitted 3 times, each time with 3 parameters.
|
||||||
|
var _watched_signals = {}
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
|
||||||
|
func _add_watched_signal(obj, name):
|
||||||
|
# SHORTCIRCUIT - ignore dupes
|
||||||
|
if(_watched_signals.has(obj) and _watched_signals[obj].has(name)):
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!_watched_signals.has(obj)):
|
||||||
|
_watched_signals[obj] = {name:[]}
|
||||||
|
else:
|
||||||
|
_watched_signals[obj][name] = []
|
||||||
|
obj.connect(name, self, '_on_watched_signal', [obj, name])
|
||||||
|
|
||||||
|
# This handles all the signals that are watched. It supports up to 9 parameters
|
||||||
|
# which could be emitted by the signal and the two parameters used when it is
|
||||||
|
# connected via watch_signal. I chose 9 since you can only specify up to 9
|
||||||
|
# parameters when dynamically calling a method via call (per the Godot
|
||||||
|
# documentation, i.e. some_object.call('some_method', 1, 2, 3...)).
|
||||||
|
#
|
||||||
|
# Based on the documentation of emit_signal, it appears you can only pass up
|
||||||
|
# to 4 parameters when firing a signal. I haven't verified this, but this should
|
||||||
|
# future proof this some if the value ever grows.
|
||||||
|
func _on_watched_signal(arg1=ARG_NOT_SET, arg2=ARG_NOT_SET, arg3=ARG_NOT_SET, \
|
||||||
|
arg4=ARG_NOT_SET, arg5=ARG_NOT_SET, arg6=ARG_NOT_SET, \
|
||||||
|
arg7=ARG_NOT_SET, arg8=ARG_NOT_SET, arg9=ARG_NOT_SET, \
|
||||||
|
arg10=ARG_NOT_SET, arg11=ARG_NOT_SET):
|
||||||
|
var args = [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]
|
||||||
|
|
||||||
|
# strip off any unused vars.
|
||||||
|
var idx = args.size() -1
|
||||||
|
while(str(args[idx]) == ARG_NOT_SET):
|
||||||
|
args.remove(idx)
|
||||||
|
idx -= 1
|
||||||
|
|
||||||
|
# retrieve object and signal name from the array and remove them. These
|
||||||
|
# will always be at the end since they are added when the connect happens.
|
||||||
|
var signal_name = args[args.size() -1]
|
||||||
|
args.pop_back()
|
||||||
|
var object = args[args.size() -1]
|
||||||
|
args.pop_back()
|
||||||
|
|
||||||
|
_watched_signals[object][signal_name].append(args)
|
||||||
|
|
||||||
|
func does_object_have_signal(object, signal_name):
|
||||||
|
var signals = object.get_signal_list()
|
||||||
|
for i in range(signals.size()):
|
||||||
|
if(signals[i]['name'] == signal_name):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
func watch_signals(object):
|
||||||
|
var signals = object.get_signal_list()
|
||||||
|
for i in range(signals.size()):
|
||||||
|
_add_watched_signal(object, signals[i]['name'])
|
||||||
|
|
||||||
|
func watch_signal(object, signal_name):
|
||||||
|
var did = false
|
||||||
|
if(does_object_have_signal(object, signal_name)):
|
||||||
|
_add_watched_signal(object, signal_name)
|
||||||
|
did = true
|
||||||
|
return did
|
||||||
|
|
||||||
|
func get_emit_count(object, signal_name):
|
||||||
|
var to_return = -1
|
||||||
|
if(is_watching(object, signal_name)):
|
||||||
|
to_return = _watched_signals[object][signal_name].size()
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func did_emit(object, signal_name):
|
||||||
|
var did = false
|
||||||
|
if(is_watching(object, signal_name)):
|
||||||
|
did = get_emit_count(object, signal_name) != 0
|
||||||
|
return did
|
||||||
|
|
||||||
|
func print_object_signals(object):
|
||||||
|
var list = object.get_signal_list()
|
||||||
|
for i in range(list.size()):
|
||||||
|
print(list[i].name, "\n ", list[i])
|
||||||
|
|
||||||
|
func get_signal_parameters(object, signal_name, index=-1):
|
||||||
|
var params = null
|
||||||
|
if(is_watching(object, signal_name)):
|
||||||
|
var all_params = _watched_signals[object][signal_name]
|
||||||
|
if(all_params.size() > 0):
|
||||||
|
if(index == -1):
|
||||||
|
index = all_params.size() -1
|
||||||
|
params = all_params[index]
|
||||||
|
return params
|
||||||
|
|
||||||
|
func is_watching_object(object):
|
||||||
|
return _watched_signals.has(object)
|
||||||
|
|
||||||
|
func is_watching(object, signal_name):
|
||||||
|
return _watched_signals.has(object) and _watched_signals[object].has(signal_name)
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
for obj in _watched_signals:
|
||||||
|
if(_utils.is_not_freed(obj)):
|
||||||
|
for signal_name in _watched_signals[obj]:
|
||||||
|
obj.disconnect(signal_name, self, '_on_watched_signal')
|
||||||
|
_watched_signals.clear()
|
||||||
|
|
||||||
|
# Returns a list of all the signal names that were emitted by the object.
|
||||||
|
# If the object is not being watched then an empty list is returned.
|
||||||
|
func get_signals_emitted(obj):
|
||||||
|
var emitted = []
|
||||||
|
if(is_watching_object(obj)):
|
||||||
|
for signal_name in _watched_signals[obj]:
|
||||||
|
if(_watched_signals[obj][signal_name].size() > 0):
|
||||||
|
emitted.append(signal_name)
|
||||||
|
|
||||||
|
return emitted
|
BIN
test_proj/addons/gut/source_code_pro.fnt
Normal file
BIN
test_proj/addons/gut/source_code_pro.fnt
Normal file
Binary file not shown.
96
test_proj/addons/gut/spy.gd
Normal file
96
test_proj/addons/gut/spy.gd
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# {
|
||||||
|
# instance_id_or_path1:{
|
||||||
|
# method1:[ [p1, p2], [p1, p2] ],
|
||||||
|
# method2:[ [p1, p2], [p1, p2] ]
|
||||||
|
# },
|
||||||
|
# instance_id_or_path1:{
|
||||||
|
# method1:[ [p1, p2], [p1, p2] ],
|
||||||
|
# method2:[ [p1, p2], [p1, p2] ]
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
var _calls = {}
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
var _lgr = _utils.get_logger()
|
||||||
|
|
||||||
|
func _get_params_as_string(params):
|
||||||
|
var to_return = ''
|
||||||
|
if(params == null):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
for i in range(params.size()):
|
||||||
|
if(params[i] == null):
|
||||||
|
to_return += 'null'
|
||||||
|
else:
|
||||||
|
if(typeof(params[i]) == TYPE_STRING):
|
||||||
|
to_return += str('"', params[i], '"')
|
||||||
|
else:
|
||||||
|
to_return += str(params[i])
|
||||||
|
if(i != params.size() -1):
|
||||||
|
to_return += ', '
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func add_call(variant, method_name, parameters=null):
|
||||||
|
if(!_calls.has(variant)):
|
||||||
|
_calls[variant] = {}
|
||||||
|
|
||||||
|
if(!_calls[variant].has(method_name)):
|
||||||
|
_calls[variant][method_name] = []
|
||||||
|
|
||||||
|
_calls[variant][method_name].append(parameters)
|
||||||
|
|
||||||
|
func was_called(variant, method_name, parameters=null):
|
||||||
|
var to_return = false
|
||||||
|
if(_calls.has(variant) and _calls[variant].has(method_name)):
|
||||||
|
if(parameters):
|
||||||
|
to_return = _calls[variant][method_name].has(parameters)
|
||||||
|
else:
|
||||||
|
to_return = true
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_call_parameters(variant, method_name, index=-1):
|
||||||
|
var to_return = null
|
||||||
|
var get_index = -1
|
||||||
|
|
||||||
|
if(_calls.has(variant) and _calls[variant].has(method_name)):
|
||||||
|
var call_size = _calls[variant][method_name].size()
|
||||||
|
if(index == -1):
|
||||||
|
# get the most recent call by default
|
||||||
|
get_index = call_size -1
|
||||||
|
else:
|
||||||
|
get_index = index
|
||||||
|
|
||||||
|
if(get_index < call_size):
|
||||||
|
to_return = _calls[variant][method_name][get_index]
|
||||||
|
else:
|
||||||
|
_lgr.error(str('Specified index ', index, ' is outside range of the number of registered calls: ', call_size))
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func call_count(instance, method_name, parameters=null):
|
||||||
|
var to_return = 0
|
||||||
|
|
||||||
|
if(was_called(instance, method_name)):
|
||||||
|
if(parameters):
|
||||||
|
for i in range(_calls[instance][method_name].size()):
|
||||||
|
if(_calls[instance][method_name][i] == parameters):
|
||||||
|
to_return += 1
|
||||||
|
else:
|
||||||
|
to_return = _calls[instance][method_name].size()
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
_calls = {}
|
||||||
|
|
||||||
|
func get_call_list_as_string(instance):
|
||||||
|
var to_return = ''
|
||||||
|
if(_calls.has(instance)):
|
||||||
|
for method in _calls[instance]:
|
||||||
|
for i in range(_calls[instance][method].size()):
|
||||||
|
to_return += str(method, '(', _get_params_as_string(_calls[instance][method][i]), ")\n")
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_logger():
|
||||||
|
return _lgr
|
||||||
|
|
||||||
|
func set_logger(logger):
|
||||||
|
_lgr = logger
|
120
test_proj/addons/gut/strutils.gd
Normal file
120
test_proj/addons/gut/strutils.gd
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
# Hash containing all the built in types in Godot. This provides an English
|
||||||
|
# name for the types that corosponds with the type constants defined in the
|
||||||
|
# engine.
|
||||||
|
var types = {}
|
||||||
|
|
||||||
|
func _init_types_dictionary():
|
||||||
|
types[TYPE_NIL] = 'TYPE_NIL'
|
||||||
|
types[TYPE_BOOL] = 'Bool'
|
||||||
|
types[TYPE_INT] = 'Int'
|
||||||
|
types[TYPE_REAL] = 'Float/Real'
|
||||||
|
types[TYPE_STRING] = 'String'
|
||||||
|
types[TYPE_VECTOR2] = 'Vector2'
|
||||||
|
types[TYPE_RECT2] = 'Rect2'
|
||||||
|
types[TYPE_VECTOR3] = 'Vector3'
|
||||||
|
#types[8] = 'Matrix32'
|
||||||
|
types[TYPE_PLANE] = 'Plane'
|
||||||
|
types[TYPE_QUAT] = 'QUAT'
|
||||||
|
types[TYPE_AABB] = 'AABB'
|
||||||
|
#types[12] = 'Matrix3'
|
||||||
|
types[TYPE_TRANSFORM] = 'Transform'
|
||||||
|
types[TYPE_COLOR] = 'Color'
|
||||||
|
#types[15] = 'Image'
|
||||||
|
types[TYPE_NODE_PATH] = 'Node Path'
|
||||||
|
types[TYPE_RID] = 'RID'
|
||||||
|
types[TYPE_OBJECT] = 'TYPE_OBJECT'
|
||||||
|
#types[19] = 'TYPE_INPUT_EVENT'
|
||||||
|
types[TYPE_DICTIONARY] = 'Dictionary'
|
||||||
|
types[TYPE_ARRAY] = 'Array'
|
||||||
|
types[TYPE_RAW_ARRAY] = 'TYPE_RAW_ARRAY'
|
||||||
|
types[TYPE_INT_ARRAY] = 'TYPE_INT_ARRAY'
|
||||||
|
types[TYPE_REAL_ARRAY] = 'TYPE_REAL_ARRAY'
|
||||||
|
types[TYPE_STRING_ARRAY] = 'TYPE_STRING_ARRAY'
|
||||||
|
types[TYPE_VECTOR2_ARRAY] = 'TYPE_VECTOR2_ARRAY'
|
||||||
|
types[TYPE_VECTOR3_ARRAY] = 'TYPE_VECTOR3_ARRAY'
|
||||||
|
types[TYPE_COLOR_ARRAY] = 'TYPE_COLOR_ARRAY'
|
||||||
|
types[TYPE_MAX] = 'TYPE_MAX'
|
||||||
|
|
||||||
|
# Types to not be formatted when using _str
|
||||||
|
var _str_ignore_types = [
|
||||||
|
TYPE_INT, TYPE_REAL, TYPE_STRING,
|
||||||
|
TYPE_NIL, TYPE_BOOL
|
||||||
|
]
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
_init_types_dictionary()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func _get_filename(path):
|
||||||
|
return path.split('/')[-1]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Gets the filename of an object passed in. This does not return the
|
||||||
|
# full path to the object, just the filename.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func _get_obj_filename(thing):
|
||||||
|
var filename = null
|
||||||
|
|
||||||
|
if(thing == null or
|
||||||
|
!is_instance_valid(thing) or
|
||||||
|
str(thing) == '[Object:null]' or
|
||||||
|
typeof(thing) != TYPE_OBJECT or
|
||||||
|
thing.has_method('__gut_instance_from_id')):
|
||||||
|
return
|
||||||
|
|
||||||
|
if(thing.get_script() == null):
|
||||||
|
if(thing is PackedScene):
|
||||||
|
filename = _get_filename(thing.resource_path)
|
||||||
|
else:
|
||||||
|
# If it isn't a packed scene and it doesn't have a script then
|
||||||
|
# we do nothing. This just read better.
|
||||||
|
pass
|
||||||
|
elif(!_utils.is_native_class(thing)):
|
||||||
|
var dict = inst2dict(thing)
|
||||||
|
filename = _get_filename(dict['@path'])
|
||||||
|
if(dict['@subpath'] != ''):
|
||||||
|
filename += str('/', dict['@subpath'])
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Better object/thing to string conversion. Includes extra details about
|
||||||
|
# whatever is passed in when it can/should.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func type2str(thing):
|
||||||
|
var filename = _get_obj_filename(thing)
|
||||||
|
var str_thing = str(thing)
|
||||||
|
|
||||||
|
if(thing == null):
|
||||||
|
# According to str there is a difference between null and an Object
|
||||||
|
# that is somehow null. To avoid getting '[Object:null]' as output
|
||||||
|
# always set it to str(null) instead of str(thing). A null object
|
||||||
|
# will pass typeof(thing) == TYPE_OBJECT check so this has to be
|
||||||
|
# before that.
|
||||||
|
str_thing = str(null)
|
||||||
|
elif(typeof(thing) in _str_ignore_types):
|
||||||
|
# do nothing b/c we already have str(thing) in
|
||||||
|
# to_return. I think this just reads a little
|
||||||
|
# better this way.
|
||||||
|
pass
|
||||||
|
elif(typeof(thing) == TYPE_OBJECT):
|
||||||
|
if(_utils.is_native_class(thing)):
|
||||||
|
str_thing = _utils.get_native_class_name(thing)
|
||||||
|
elif(_utils.is_double(thing)):
|
||||||
|
var double_path = _get_filename(thing.__gut_metadata_.path)
|
||||||
|
if(thing.__gut_metadata_.subpath != ''):
|
||||||
|
double_path += str('/', thing.__gut_metadata_.subpath)
|
||||||
|
|
||||||
|
str_thing += '(double of ' + double_path + ')'
|
||||||
|
filename = null
|
||||||
|
elif(types.has(typeof(thing))):
|
||||||
|
if(!str_thing.begins_with('(')):
|
||||||
|
str_thing = '(' + str_thing + ')'
|
||||||
|
str_thing = str(types[typeof(thing)], str_thing)
|
||||||
|
|
||||||
|
if(filename != null):
|
||||||
|
str_thing += str('(', filename, ')')
|
||||||
|
return str_thing
|
43
test_proj/addons/gut/stub_params.gd
Normal file
43
test_proj/addons/gut/stub_params.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
var return_val = null
|
||||||
|
var stub_target = null
|
||||||
|
var target_subpath = null
|
||||||
|
var parameters = null
|
||||||
|
var stub_method = null
|
||||||
|
var call_super = false
|
||||||
|
|
||||||
|
const NOT_SET = '|_1_this_is_not_set_1_|'
|
||||||
|
|
||||||
|
func _init(target=null, method=null, subpath=null):
|
||||||
|
stub_target = target
|
||||||
|
stub_method = method
|
||||||
|
target_subpath = subpath
|
||||||
|
|
||||||
|
func to_return(val):
|
||||||
|
return_val = val
|
||||||
|
call_super = false
|
||||||
|
return self
|
||||||
|
|
||||||
|
func to_do_nothing():
|
||||||
|
return to_return(null)
|
||||||
|
|
||||||
|
func to_call_super():
|
||||||
|
call_super = true
|
||||||
|
return self
|
||||||
|
|
||||||
|
func when_passed(p1=NOT_SET,p2=NOT_SET,p3=NOT_SET,p4=NOT_SET,p5=NOT_SET,p6=NOT_SET,p7=NOT_SET,p8=NOT_SET,p9=NOT_SET,p10=NOT_SET):
|
||||||
|
parameters = [p1,p2,p3,p4,p5,p6,p7,p8,p9,p10]
|
||||||
|
var idx = 0
|
||||||
|
while(idx < parameters.size()):
|
||||||
|
if(str(parameters[idx]) == NOT_SET):
|
||||||
|
parameters.remove(idx)
|
||||||
|
else:
|
||||||
|
idx += 1
|
||||||
|
return self
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var base_string = str(stub_target, '[', target_subpath, '].', stub_method)
|
||||||
|
if(call_super):
|
||||||
|
base_string += " to call SUPER"
|
||||||
|
else:
|
||||||
|
base_string += str(' with (', parameters, ') = ', return_val)
|
||||||
|
return base_string
|
163
test_proj/addons/gut/stubber.gd
Normal file
163
test_proj/addons/gut/stubber.gd
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# {
|
||||||
|
# inst_id_or_path1:{
|
||||||
|
# method_name1: [StubParams, StubParams],
|
||||||
|
# method_name2: [StubParams, StubParams]
|
||||||
|
# },
|
||||||
|
# inst_id_or_path2:{
|
||||||
|
# method_name1: [StubParams, StubParams],
|
||||||
|
# method_name2: [StubParams, StubParams]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
var returns = {}
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
var _lgr = _utils.get_logger()
|
||||||
|
var _strutils = _utils.Strutils.new()
|
||||||
|
|
||||||
|
func _is_instance(obj):
|
||||||
|
return typeof(obj) == TYPE_OBJECT and !obj.has_method('new')
|
||||||
|
|
||||||
|
func _make_key_from_metadata(doubled):
|
||||||
|
var to_return = doubled.__gut_metadata_.path
|
||||||
|
if(doubled.__gut_metadata_.subpath != ''):
|
||||||
|
to_return += str('-', doubled.__gut_metadata_.subpath)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# Creates they key for the returns hash based on the type of object passed in
|
||||||
|
# obj could be a string of a path to a script with an optional subpath or
|
||||||
|
# it could be an instance of a doubled object.
|
||||||
|
func _make_key_from_variant(obj, subpath=null):
|
||||||
|
var to_return = null
|
||||||
|
|
||||||
|
match typeof(obj):
|
||||||
|
TYPE_STRING:
|
||||||
|
# this has to match what is done in _make_key_from_metadata
|
||||||
|
to_return = obj
|
||||||
|
if(subpath != null and subpath != ''):
|
||||||
|
to_return += str('-', subpath)
|
||||||
|
TYPE_OBJECT:
|
||||||
|
if(_is_instance(obj)):
|
||||||
|
to_return = _make_key_from_metadata(obj)
|
||||||
|
elif(_utils.is_native_class(obj)):
|
||||||
|
to_return = _utils.get_native_class_name(obj)
|
||||||
|
else:
|
||||||
|
to_return = obj.resource_path
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func _add_obj_method(obj, method, subpath=null):
|
||||||
|
var key = _make_key_from_variant(obj, subpath)
|
||||||
|
if(_is_instance(obj)):
|
||||||
|
key = obj
|
||||||
|
|
||||||
|
if(!returns.has(key)):
|
||||||
|
returns[key] = {}
|
||||||
|
if(!returns[key].has(method)):
|
||||||
|
returns[key][method] = []
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
# ##############
|
||||||
|
# Public
|
||||||
|
# ##############
|
||||||
|
|
||||||
|
# TODO: This method is only used in tests and should be refactored out. It
|
||||||
|
# does not support inner classes and isn't helpful.
|
||||||
|
func set_return(obj, method, value, parameters=null):
|
||||||
|
var key = _add_obj_method(obj, method)
|
||||||
|
var sp = _utils.StubParams.new(key, method)
|
||||||
|
sp.parameters = parameters
|
||||||
|
sp.return_val = value
|
||||||
|
returns[key][method].append(sp)
|
||||||
|
|
||||||
|
func add_stub(stub_params):
|
||||||
|
var key = _add_obj_method(stub_params.stub_target, stub_params.stub_method, stub_params.target_subpath)
|
||||||
|
returns[key][stub_params.stub_method].append(stub_params)
|
||||||
|
|
||||||
|
# Searches returns for an entry that matches the instance or the class that
|
||||||
|
# passed in obj is.
|
||||||
|
#
|
||||||
|
# obj can be an instance, class, or a path.
|
||||||
|
func _find_stub(obj, method, parameters=null):
|
||||||
|
var key = _make_key_from_variant(obj)
|
||||||
|
var to_return = null
|
||||||
|
|
||||||
|
if(_is_instance(obj)):
|
||||||
|
if(returns.has(obj) and returns[obj].has(method)):
|
||||||
|
key = obj
|
||||||
|
elif(obj.get('__gut_metadata_')):
|
||||||
|
key = _make_key_from_metadata(obj)
|
||||||
|
|
||||||
|
if(returns.has(key) and returns[key].has(method)):
|
||||||
|
var param_idx = -1
|
||||||
|
var null_idx = -1
|
||||||
|
|
||||||
|
for i in range(returns[key][method].size()):
|
||||||
|
if(returns[key][method][i].parameters == parameters):
|
||||||
|
param_idx = i
|
||||||
|
if(returns[key][method][i].parameters == null):
|
||||||
|
null_idx = i
|
||||||
|
|
||||||
|
# We have matching parameter values so return the stub value for that
|
||||||
|
if(param_idx != -1):
|
||||||
|
to_return = returns[key][method][param_idx]
|
||||||
|
# We found a case where the parameters were not specified so return
|
||||||
|
# parameters for that
|
||||||
|
elif(null_idx != -1):
|
||||||
|
to_return = returns[key][method][null_idx]
|
||||||
|
else:
|
||||||
|
_lgr.warn(str('Call to [', method, '] was not stubbed for the supplied parameters ', parameters, '. Null was returned.'))
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# Gets a stubbed return value for the object and method passed in. If the
|
||||||
|
# instance was stubbed it will use that, otherwise it will use the path and
|
||||||
|
# subpath of the object to try to find a value.
|
||||||
|
#
|
||||||
|
# It will also use the optional list of parameter values to find a value. If
|
||||||
|
# the object was stubbed with no parameters than any parameters will match.
|
||||||
|
# If it was stubbed with specific parameter values then it will try to match.
|
||||||
|
# If the parameters do not match BUT there was also an empty parameter list stub
|
||||||
|
# then it will return those.
|
||||||
|
# If it cannot find anything that matches then null is returned.for
|
||||||
|
#
|
||||||
|
# Parameters
|
||||||
|
# obj: this should be an instance of a doubled object.
|
||||||
|
# method: the method called
|
||||||
|
# parameters: optional array of parameter vales to find a return value for.
|
||||||
|
func get_return(obj, method, parameters=null):
|
||||||
|
var stub_info = _find_stub(obj, method, parameters)
|
||||||
|
|
||||||
|
if(stub_info != null):
|
||||||
|
return stub_info.return_val
|
||||||
|
else:
|
||||||
|
return null
|
||||||
|
|
||||||
|
func should_call_super(obj, method, parameters=null):
|
||||||
|
var stub_info = _find_stub(obj, method, parameters)
|
||||||
|
if(stub_info != null):
|
||||||
|
return stub_info.call_super
|
||||||
|
else:
|
||||||
|
# this log message is here because of how the generated doubled scripts
|
||||||
|
# are structured. With this log msg here, you will only see one
|
||||||
|
# "unstubbed" info instead of multiple.
|
||||||
|
_lgr.info('Unstubbed call to ' + method + '::' + _strutils.type2str(obj))
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
returns.clear()
|
||||||
|
|
||||||
|
func get_logger():
|
||||||
|
return _lgr
|
||||||
|
|
||||||
|
func set_logger(logger):
|
||||||
|
_lgr = logger
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var text = ''
|
||||||
|
for thing in returns:
|
||||||
|
text += str(thing) + "\n"
|
||||||
|
for method in returns[thing]:
|
||||||
|
text += str("\t", method, "\n")
|
||||||
|
for i in range(returns[thing][method].size()):
|
||||||
|
text += "\t\t" + returns[thing][method][i].to_s() + "\n"
|
||||||
|
return text
|
172
test_proj/addons/gut/summary.gd
Normal file
172
test_proj/addons/gut/summary.gd
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Contains all the results of a single test. Allows for multiple asserts results
|
||||||
|
# and pending calls.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class Test:
|
||||||
|
var pass_texts = []
|
||||||
|
var fail_texts = []
|
||||||
|
var pending_texts = []
|
||||||
|
|
||||||
|
# NOTE: The "failed" and "pending" text must match what is outputted by
|
||||||
|
# the logger in order for text highlighting to occur in summary.
|
||||||
|
func to_s():
|
||||||
|
var pad = ' '
|
||||||
|
var to_return = ''
|
||||||
|
for i in range(fail_texts.size()):
|
||||||
|
to_return += str(pad, '[Failed]: ', fail_texts[i], "\n")
|
||||||
|
for i in range(pending_texts.size()):
|
||||||
|
to_return += str(pad, '[Pending]: ', pending_texts[i], "\n")
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Contains all the results for a single test-script/inner class. Persists the
|
||||||
|
# names of the tests and results and the order in which the tests were run.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class TestScript:
|
||||||
|
var name = 'NOT_SET'
|
||||||
|
#
|
||||||
|
var _tests = {}
|
||||||
|
var _test_order = []
|
||||||
|
|
||||||
|
func _init(script_name):
|
||||||
|
name = script_name
|
||||||
|
|
||||||
|
func get_pass_count():
|
||||||
|
var count = 0
|
||||||
|
for key in _tests:
|
||||||
|
count += _tests[key].pass_texts.size()
|
||||||
|
return count
|
||||||
|
|
||||||
|
func get_fail_count():
|
||||||
|
var count = 0
|
||||||
|
for key in _tests:
|
||||||
|
count += _tests[key].fail_texts.size()
|
||||||
|
return count
|
||||||
|
|
||||||
|
func get_pending_count():
|
||||||
|
var count = 0
|
||||||
|
for key in _tests:
|
||||||
|
count += _tests[key].pending_texts.size()
|
||||||
|
return count
|
||||||
|
|
||||||
|
func get_test_obj(obj_name):
|
||||||
|
if(!_tests.has(obj_name)):
|
||||||
|
_tests[obj_name] = Test.new()
|
||||||
|
_test_order.append(obj_name)
|
||||||
|
return _tests[obj_name]
|
||||||
|
|
||||||
|
func add_pass(test_name, reason):
|
||||||
|
var t = get_test_obj(test_name)
|
||||||
|
t.pass_texts.append(reason)
|
||||||
|
|
||||||
|
func add_fail(test_name, reason):
|
||||||
|
var t = get_test_obj(test_name)
|
||||||
|
t.fail_texts.append(reason)
|
||||||
|
|
||||||
|
func add_pending(test_name, reason):
|
||||||
|
var t = get_test_obj(test_name)
|
||||||
|
t.pending_texts.append(reason)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Summary Class
|
||||||
|
#
|
||||||
|
# This class holds the results of all the test scripts and Inner Classes that
|
||||||
|
# were run.
|
||||||
|
# -------------------------------------------d-----------------------------------
|
||||||
|
var _scripts = []
|
||||||
|
|
||||||
|
func add_script(name):
|
||||||
|
_scripts.append(TestScript.new(name))
|
||||||
|
|
||||||
|
func get_scripts():
|
||||||
|
return _scripts
|
||||||
|
|
||||||
|
func get_current_script():
|
||||||
|
return _scripts[_scripts.size() - 1]
|
||||||
|
|
||||||
|
func add_test(test_name):
|
||||||
|
get_current_script().get_test_obj(test_name)
|
||||||
|
|
||||||
|
func add_pass(test_name, reason = ''):
|
||||||
|
get_current_script().add_pass(test_name, reason)
|
||||||
|
|
||||||
|
func add_fail(test_name, reason = ''):
|
||||||
|
get_current_script().add_fail(test_name, reason)
|
||||||
|
|
||||||
|
func add_pending(test_name, reason = ''):
|
||||||
|
get_current_script().add_pending(test_name, reason)
|
||||||
|
|
||||||
|
func get_test_text(test_name):
|
||||||
|
return test_name + "\n" + get_current_script().get_test_obj(test_name).to_s()
|
||||||
|
|
||||||
|
# Gets the count of unique script names minus the .<Inner Class Name> at the
|
||||||
|
# end. Used for displaying the number of scripts without including all the
|
||||||
|
# Inner Classes.
|
||||||
|
func get_non_inner_class_script_count():
|
||||||
|
var unique_scripts = {}
|
||||||
|
for i in range(_scripts.size()):
|
||||||
|
var ext_loc = _scripts[i].name.find_last('.gd.')
|
||||||
|
if(ext_loc == -1):
|
||||||
|
unique_scripts[_scripts[i].name] = 1
|
||||||
|
else:
|
||||||
|
unique_scripts[_scripts[i].name.substr(0, ext_loc + 3)] = 1
|
||||||
|
return unique_scripts.keys().size()
|
||||||
|
|
||||||
|
func get_totals():
|
||||||
|
var totals = {
|
||||||
|
passing = 0,
|
||||||
|
pending = 0,
|
||||||
|
failing = 0,
|
||||||
|
tests = 0,
|
||||||
|
scripts = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for s in range(_scripts.size()):
|
||||||
|
totals.passing += _scripts[s].get_pass_count()
|
||||||
|
totals.pending += _scripts[s].get_pending_count()
|
||||||
|
totals.failing += _scripts[s].get_fail_count()
|
||||||
|
totals.tests += _scripts[s]._test_order.size()
|
||||||
|
|
||||||
|
totals.scripts = get_non_inner_class_script_count()
|
||||||
|
|
||||||
|
return totals
|
||||||
|
|
||||||
|
func log_summary_text(lgr):
|
||||||
|
var orig_indent = lgr.get_indent_level()
|
||||||
|
var found_failing_or_pending = false
|
||||||
|
|
||||||
|
for s in range(_scripts.size()):
|
||||||
|
lgr.set_indent_level(0)
|
||||||
|
if(_scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0):
|
||||||
|
lgr.log(_scripts[s].name, lgr.fmts.underline)
|
||||||
|
|
||||||
|
|
||||||
|
for t in range(_scripts[s]._test_order.size()):
|
||||||
|
var tname = _scripts[s]._test_order[t]
|
||||||
|
var test = _scripts[s].get_test_obj(tname)
|
||||||
|
if(test.fail_texts.size() > 0 or test.pending_texts.size() > 0):
|
||||||
|
found_failing_or_pending = true
|
||||||
|
lgr.log(str('- ', tname))
|
||||||
|
lgr.inc_indent()
|
||||||
|
|
||||||
|
for i in range(test.fail_texts.size()):
|
||||||
|
lgr.failed(test.fail_texts[i])
|
||||||
|
for i in range(test.pending_texts.size()):
|
||||||
|
lgr.pending(test.pending_texts[i])
|
||||||
|
lgr.dec_indent()
|
||||||
|
|
||||||
|
lgr.set_indent_level(0)
|
||||||
|
if(!found_failing_or_pending):
|
||||||
|
lgr.log('All tests passed', lgr.fmts.green)
|
||||||
|
|
||||||
|
lgr.log()
|
||||||
|
var _totals = get_totals()
|
||||||
|
lgr.log("Totals", lgr.fmts.yellow)
|
||||||
|
lgr.log(str('Scripts: ', get_non_inner_class_script_count()))
|
||||||
|
lgr.log(str('Tests: ', _totals.tests))
|
||||||
|
lgr.log(str('Passing asserts: ', _totals.passing))
|
||||||
|
lgr.log(str('Failing asserts: ',_totals.failing))
|
||||||
|
lgr.log(str('Pending: ', _totals.pending))
|
||||||
|
|
||||||
|
lgr.set_indent_level(orig_indent)
|
||||||
|
|
1219
test_proj/addons/gut/test.gd
Normal file
1219
test_proj/addons/gut/test.gd
Normal file
File diff suppressed because it is too large
Load Diff
282
test_proj/addons/gut/test_collector.gd
Normal file
282
test_proj/addons/gut/test_collector.gd
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Used to keep track of info about each test ran.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class Test:
|
||||||
|
# indicator if it passed or not. defaults to true since it takes only
|
||||||
|
# one failure to make it not pass. _fail in gut will set this.
|
||||||
|
var passed = true
|
||||||
|
# the name of the function
|
||||||
|
var name = ""
|
||||||
|
# flag to know if the name has been printed yet.
|
||||||
|
var has_printed_name = false
|
||||||
|
# the line number the test is on
|
||||||
|
var line_number = -1
|
||||||
|
# the number of arguments the method has
|
||||||
|
var arg_count = 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# This holds all the meta information for a test script. It contains the
|
||||||
|
# name of the inner class and an array of Test "structs".
|
||||||
|
#
|
||||||
|
# This class also facilitates all the exporting and importing of tests.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class TestScript:
|
||||||
|
var inner_class_name = null
|
||||||
|
var tests = []
|
||||||
|
var path = null
|
||||||
|
var _utils = null
|
||||||
|
var _lgr = null
|
||||||
|
|
||||||
|
func _init(utils=null, logger=null):
|
||||||
|
_utils = utils
|
||||||
|
_lgr = logger
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var to_return = path
|
||||||
|
if(inner_class_name != null):
|
||||||
|
to_return += str('.', inner_class_name)
|
||||||
|
to_return += "\n"
|
||||||
|
for i in range(tests.size()):
|
||||||
|
to_return += str(' ', tests[i].name, "\n")
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_new():
|
||||||
|
return load_script().new()
|
||||||
|
|
||||||
|
func load_script():
|
||||||
|
#print('loading: ', get_full_name())
|
||||||
|
var to_return = load(path)
|
||||||
|
if(inner_class_name != null):
|
||||||
|
# If we wanted to do inner classes in inner classses
|
||||||
|
# then this would have to become some kind of loop or recursive
|
||||||
|
# call to go all the way down the chain or this class would
|
||||||
|
# have to change to hold onto the loaded class instead of
|
||||||
|
# just path information.
|
||||||
|
to_return = to_return.get(inner_class_name)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_filename_and_inner():
|
||||||
|
var to_return = get_filename()
|
||||||
|
if(inner_class_name != null):
|
||||||
|
to_return += '.' + inner_class_name
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_full_name():
|
||||||
|
var to_return = path
|
||||||
|
if(inner_class_name != null):
|
||||||
|
to_return += '.' + inner_class_name
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_filename():
|
||||||
|
return path.get_file()
|
||||||
|
|
||||||
|
func has_inner_class():
|
||||||
|
return inner_class_name != null
|
||||||
|
|
||||||
|
# Note: although this no longer needs to export the inner_class names since
|
||||||
|
# they are pulled from metadata now, it is easier to leave that in
|
||||||
|
# so we don't have to cut the export down to unique script names.
|
||||||
|
func export_to(config_file, section):
|
||||||
|
config_file.set_value(section, 'path', path)
|
||||||
|
config_file.set_value(section, 'inner_class', inner_class_name)
|
||||||
|
var names = []
|
||||||
|
for i in range(tests.size()):
|
||||||
|
names.append(tests[i].name)
|
||||||
|
config_file.set_value(section, 'tests', names)
|
||||||
|
|
||||||
|
func _remap_path(source_path):
|
||||||
|
var to_return = source_path
|
||||||
|
if(!_utils.file_exists(source_path)):
|
||||||
|
_lgr.debug('Checking for remap for: ' + source_path)
|
||||||
|
var remap_path = source_path.get_basename() + '.gd.remap'
|
||||||
|
if(_utils.file_exists(remap_path)):
|
||||||
|
var cf = ConfigFile.new()
|
||||||
|
cf.load(remap_path)
|
||||||
|
to_return = cf.get_value('remap', 'path')
|
||||||
|
else:
|
||||||
|
_lgr.warn('Could not find remap file ' + remap_path)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func import_from(config_file, section):
|
||||||
|
path = config_file.get_value(section, 'path')
|
||||||
|
path = _remap_path(path)
|
||||||
|
# Null is an acceptable value, but you can't pass null as a default to
|
||||||
|
# get_value since it thinks you didn't send a default...then it spits
|
||||||
|
# out red text. This works around that.
|
||||||
|
var inner_name = config_file.get_value(section, 'inner_class', 'Placeholder')
|
||||||
|
if(inner_name != 'Placeholder'):
|
||||||
|
inner_class_name = inner_name
|
||||||
|
else: # just being explicit
|
||||||
|
inner_class_name = null
|
||||||
|
|
||||||
|
func get_test_named(name):
|
||||||
|
return _utils.search_array(tests, 'name', name)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# start test_collector, I don't think I like the name.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
var scripts = []
|
||||||
|
var _test_prefix = 'test_'
|
||||||
|
var _test_class_prefix = 'Test'
|
||||||
|
|
||||||
|
var _utils = load('res://addons/gut/utils.gd').get_instance()
|
||||||
|
var _lgr = _utils.get_logger()
|
||||||
|
|
||||||
|
func _does_inherit_from_test(thing):
|
||||||
|
var base_script = thing.get_base_script()
|
||||||
|
var to_return = false
|
||||||
|
if(base_script != null):
|
||||||
|
var base_path = base_script.get_path()
|
||||||
|
if(base_path == 'res://addons/gut/test.gd'):
|
||||||
|
to_return = true
|
||||||
|
else:
|
||||||
|
to_return = _does_inherit_from_test(base_script)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func _populate_tests(test_script):
|
||||||
|
var methods = test_script.load_script().get_script_method_list()
|
||||||
|
for i in range(methods.size()):
|
||||||
|
var name = methods[i]['name']
|
||||||
|
if(name.begins_with(_test_prefix)):
|
||||||
|
var t = Test.new()
|
||||||
|
t.name = name
|
||||||
|
t.arg_count = methods[i]['args'].size()
|
||||||
|
test_script.tests.append(t)
|
||||||
|
|
||||||
|
func _get_inner_test_class_names(loaded):
|
||||||
|
var inner_classes = []
|
||||||
|
var const_map = loaded.get_script_constant_map()
|
||||||
|
for key in const_map:
|
||||||
|
var thing = const_map[key]
|
||||||
|
if(typeof(thing) == TYPE_OBJECT):
|
||||||
|
if(key.begins_with(_test_class_prefix)):
|
||||||
|
if(_does_inherit_from_test(thing)):
|
||||||
|
inner_classes.append(key)
|
||||||
|
else:
|
||||||
|
_lgr.warn(str('Ignoring Inner Class ', key,
|
||||||
|
' because it does not extend res://addons/gut/test.gd'))
|
||||||
|
|
||||||
|
# This could go deeper and find inner classes within inner classes
|
||||||
|
# but requires more experimentation. Right now I'm keeping it at
|
||||||
|
# one level since that is what the previous version did and there
|
||||||
|
# has been no demand for deeper nesting.
|
||||||
|
# _populate_inner_test_classes(thing)
|
||||||
|
return inner_classes
|
||||||
|
|
||||||
|
func _parse_script(test_script):
|
||||||
|
var inner_classes = []
|
||||||
|
var scripts_found = []
|
||||||
|
|
||||||
|
var loaded = load(test_script.path)
|
||||||
|
if(_does_inherit_from_test(loaded)):
|
||||||
|
_populate_tests(test_script)
|
||||||
|
scripts_found.append(test_script.path)
|
||||||
|
inner_classes = _get_inner_test_class_names(loaded)
|
||||||
|
|
||||||
|
for i in range(inner_classes.size()):
|
||||||
|
var loaded_inner = loaded.get(inner_classes[i])
|
||||||
|
if(_does_inherit_from_test(loaded_inner)):
|
||||||
|
var ts = TestScript.new(_utils, _lgr)
|
||||||
|
ts.path = test_script.path
|
||||||
|
ts.inner_class_name = inner_classes[i]
|
||||||
|
_populate_tests(ts)
|
||||||
|
scripts.append(ts)
|
||||||
|
scripts_found.append(test_script.path + '[' + inner_classes[i] +']')
|
||||||
|
|
||||||
|
return scripts_found
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# Public
|
||||||
|
# -----------------
|
||||||
|
func add_script(path):
|
||||||
|
# SHORTCIRCUIT
|
||||||
|
if(has_script(path)):
|
||||||
|
return []
|
||||||
|
|
||||||
|
var f = File.new()
|
||||||
|
# SHORTCIRCUIT
|
||||||
|
if(!f.file_exists(path)):
|
||||||
|
_lgr.error('Could not find script: ' + path)
|
||||||
|
return
|
||||||
|
|
||||||
|
var ts = TestScript.new(_utils, _lgr)
|
||||||
|
ts.path = path
|
||||||
|
scripts.append(ts)
|
||||||
|
return _parse_script(ts)
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
scripts.clear()
|
||||||
|
|
||||||
|
func has_script(path):
|
||||||
|
var found = false
|
||||||
|
var idx = 0
|
||||||
|
while(idx < scripts.size() and !found):
|
||||||
|
if(scripts[idx].get_full_name() == path):
|
||||||
|
found = true
|
||||||
|
else:
|
||||||
|
idx += 1
|
||||||
|
return found
|
||||||
|
|
||||||
|
func export_tests(path):
|
||||||
|
var success = true
|
||||||
|
var f = ConfigFile.new()
|
||||||
|
for i in range(scripts.size()):
|
||||||
|
scripts[i].export_to(f, str('TestScript-', i))
|
||||||
|
var result = f.save(path)
|
||||||
|
if(result != OK):
|
||||||
|
_lgr.error(str('Could not save exported tests to [', path, ']. Error code: ', result))
|
||||||
|
success = false
|
||||||
|
return success
|
||||||
|
|
||||||
|
func import_tests(path):
|
||||||
|
var success = false
|
||||||
|
var f = ConfigFile.new()
|
||||||
|
var result = f.load(path)
|
||||||
|
if(result != OK):
|
||||||
|
_lgr.error(str('Could not load exported tests from [', path, ']. Error code: ', result))
|
||||||
|
else:
|
||||||
|
var sections = f.get_sections()
|
||||||
|
for key in sections:
|
||||||
|
var ts = TestScript.new(_utils, _lgr)
|
||||||
|
ts.import_from(f, key)
|
||||||
|
_populate_tests(ts)
|
||||||
|
scripts.append(ts)
|
||||||
|
success = true
|
||||||
|
return success
|
||||||
|
|
||||||
|
func get_script_named(name):
|
||||||
|
return _utils.search_array(scripts, 'get_filename_and_inner', name)
|
||||||
|
|
||||||
|
func get_test_named(script_name, test_name):
|
||||||
|
var s = get_script_named(script_name)
|
||||||
|
if(s != null):
|
||||||
|
return s.get_test_named(test_name)
|
||||||
|
else:
|
||||||
|
return null
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var to_return = ''
|
||||||
|
for i in range(scripts.size()):
|
||||||
|
to_return += scripts[i].to_s() + "\n"
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
# ---------------------
|
||||||
|
# Accessors
|
||||||
|
# ---------------------
|
||||||
|
func get_logger():
|
||||||
|
return _lgr
|
||||||
|
|
||||||
|
func set_logger(logger):
|
||||||
|
_lgr = logger
|
||||||
|
|
||||||
|
func get_test_prefix():
|
||||||
|
return _test_prefix
|
||||||
|
|
||||||
|
func set_test_prefix(test_prefix):
|
||||||
|
_test_prefix = test_prefix
|
||||||
|
|
||||||
|
func get_test_class_prefix():
|
||||||
|
return _test_class_prefix
|
||||||
|
|
||||||
|
func set_test_class_prefix(test_class_prefix):
|
||||||
|
_test_class_prefix = test_class_prefix
|
43
test_proj/addons/gut/thing_counter.gd
Normal file
43
test_proj/addons/gut/thing_counter.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
var things = {}
|
||||||
|
|
||||||
|
func get_unique_count():
|
||||||
|
return things.size()
|
||||||
|
|
||||||
|
func add(thing):
|
||||||
|
if(things.has(thing)):
|
||||||
|
things[thing] += 1
|
||||||
|
else:
|
||||||
|
things[thing] = 1
|
||||||
|
|
||||||
|
func has(thing):
|
||||||
|
return things.has(thing)
|
||||||
|
|
||||||
|
func get(thing):
|
||||||
|
var to_return = 0
|
||||||
|
if(things.has(thing)):
|
||||||
|
to_return = things[thing]
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func sum():
|
||||||
|
var count = 0
|
||||||
|
for key in things:
|
||||||
|
count += things[key]
|
||||||
|
return count
|
||||||
|
|
||||||
|
func to_s():
|
||||||
|
var to_return = ""
|
||||||
|
for key in things:
|
||||||
|
to_return += str(key, ": ", things[key], "\n")
|
||||||
|
to_return += str("sum: ", sum())
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
func get_max_count():
|
||||||
|
var max_val = null
|
||||||
|
for key in things:
|
||||||
|
if(max_val == null or things[key] > max_val):
|
||||||
|
max_val = things[key]
|
||||||
|
return max_val
|
||||||
|
|
||||||
|
func add_array_items(array):
|
||||||
|
for i in range(array.size()):
|
||||||
|
add(array[i])
|
312
test_proj/addons/gut/utils.gd
Normal file
312
test_proj/addons/gut/utils.gd
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
# ##############################################################################
|
||||||
|
#(G)odot (U)nit (T)est class
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# =====================
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Tom "Butch" Wesley
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
# Description
|
||||||
|
# -----------
|
||||||
|
# This class is a PSUEDO SINGLETON. You should not make instances of it but use
|
||||||
|
# the get_instance static method.
|
||||||
|
# ##############################################################################
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# The instance name as a function since you can't have static variables.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
static func INSTANCE_NAME():
|
||||||
|
return '__GutUtilsInstName__'
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Gets the root node without having to be in the tree and pushing out an error
|
||||||
|
# if we don't have a main loop ready to go yet.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
static func get_root_node():
|
||||||
|
var to_return = null
|
||||||
|
var main_loop = Engine.get_main_loop()
|
||||||
|
if(main_loop != null):
|
||||||
|
return main_loop.root
|
||||||
|
else:
|
||||||
|
push_error('No Main Loop Yet')
|
||||||
|
return null
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Get the ONE instance of utils
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
static func get_instance():
|
||||||
|
var the_root = get_root_node()
|
||||||
|
var inst = null
|
||||||
|
if(the_root.has_node(INSTANCE_NAME())):
|
||||||
|
inst = the_root.get_node(INSTANCE_NAME())
|
||||||
|
else:
|
||||||
|
inst = load('res://addons/gut/utils.gd').new()
|
||||||
|
inst.set_name(INSTANCE_NAME())
|
||||||
|
the_root.add_child(inst)
|
||||||
|
return inst
|
||||||
|
|
||||||
|
var Logger = load('res://addons/gut/logger.gd') # everything should use get_logger
|
||||||
|
var _lgr = null
|
||||||
|
|
||||||
|
var _test_mode = false
|
||||||
|
var AutoFree = load('res://addons/gut/autofree.gd')
|
||||||
|
var Doubler = load('res://addons/gut/doubler.gd')
|
||||||
|
var Gut = load('res://addons/gut/gut.gd')
|
||||||
|
var HookScript = load('res://addons/gut/hook_script.gd')
|
||||||
|
var MethodMaker = load('res://addons/gut/method_maker.gd')
|
||||||
|
var OneToMany = load('res://addons/gut/one_to_many.gd')
|
||||||
|
var OrphanCounter = load('res://addons/gut/orphan_counter.gd')
|
||||||
|
var ParameterFactory = load('res://addons/gut/parameter_factory.gd')
|
||||||
|
var ParameterHandler = load('res://addons/gut/parameter_handler.gd')
|
||||||
|
var Printers = load('res://addons/gut/printers.gd')
|
||||||
|
var Spy = load('res://addons/gut/spy.gd')
|
||||||
|
var Strutils = load('res://addons/gut/strutils.gd')
|
||||||
|
var Stubber = load('res://addons/gut/stubber.gd')
|
||||||
|
var StubParams = load('res://addons/gut/stub_params.gd')
|
||||||
|
var Summary = load('res://addons/gut/summary.gd')
|
||||||
|
var Test = load('res://addons/gut/test.gd')
|
||||||
|
var TestCollector = load('res://addons/gut/test_collector.gd')
|
||||||
|
var ThingCounter = load('res://addons/gut/thing_counter.gd')
|
||||||
|
|
||||||
|
# Source of truth for the GUT version
|
||||||
|
var version = '7.0.0'
|
||||||
|
# The required Godot version as an array.
|
||||||
|
var req_godot = [3, 2, 0]
|
||||||
|
# Used for doing file manipulation stuff so as to not keep making File instances.
|
||||||
|
# could be a bit of overkill but who cares.
|
||||||
|
var _file_checker = File.new()
|
||||||
|
|
||||||
|
const GUT_METADATA = '__gut_metadata_'
|
||||||
|
|
||||||
|
enum DOUBLE_STRATEGY{
|
||||||
|
FULL,
|
||||||
|
PARTIAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Blurb of text with GUT and Godot versions.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func get_version_text():
|
||||||
|
var v_info = Engine.get_version_info()
|
||||||
|
var gut_version_info = str('GUT version: ', version)
|
||||||
|
var godot_version_info = str('Godot version: ', v_info.major, '.', v_info.minor, '.', v_info.patch)
|
||||||
|
return godot_version_info + "\n" + gut_version_info
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Returns a nice string for erroring out when we have a bad Godot version.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func get_bad_version_text():
|
||||||
|
var ver = join_array(req_godot, '.')
|
||||||
|
var info = Engine.get_version_info()
|
||||||
|
var gd_version = str(info.major, '.', info.minor, '.', info.patch)
|
||||||
|
return 'GUT ' + version + ' requires Godot ' + ver + ' or greater. Godot version is ' + gd_version
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Checks the Godot version against req_godot array.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func is_version_ok():
|
||||||
|
var info = Engine.get_version_info()
|
||||||
|
var is_ok = info.major >= req_godot[0] and \
|
||||||
|
info.minor >= req_godot[1] and \
|
||||||
|
info.patch >= req_godot[2]
|
||||||
|
return is_ok
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Everything should get a logger through this.
|
||||||
|
#
|
||||||
|
# When running in test mode this will always return a new logger so that errors
|
||||||
|
# are not caused by getting bad warn/error/etc counts.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func get_logger():
|
||||||
|
if(_test_mode):
|
||||||
|
return Logger.new()
|
||||||
|
else:
|
||||||
|
if(_lgr == null):
|
||||||
|
_lgr = Logger.new()
|
||||||
|
return _lgr
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Returns an array created by splitting the string by the delimiter
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func split_string(to_split, delim):
|
||||||
|
var to_return = []
|
||||||
|
|
||||||
|
var loc = to_split.find(delim)
|
||||||
|
while(loc != -1):
|
||||||
|
to_return.append(to_split.substr(0, loc))
|
||||||
|
to_split = to_split.substr(loc + 1, to_split.length() - loc)
|
||||||
|
loc = to_split.find(delim)
|
||||||
|
to_return.append(to_split)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Returns a string containing all the elements in the array separated by delim
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func join_array(a, delim):
|
||||||
|
var to_return = ''
|
||||||
|
for i in range(a.size()):
|
||||||
|
to_return += str(a[i])
|
||||||
|
if(i != a.size() -1):
|
||||||
|
to_return += str(delim)
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# return if_null if value is null otherwise return value
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func nvl(value, if_null):
|
||||||
|
if(value == null):
|
||||||
|
return if_null
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# returns true if the object has been freed, false if not
|
||||||
|
#
|
||||||
|
# From what i've read, the weakref approach should work. It seems to work most
|
||||||
|
# of the time but sometimes it does not catch it. The str comparison seems to
|
||||||
|
# fill in the gaps. I've not seen any errors after adding that check.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func is_freed(obj):
|
||||||
|
var wr = weakref(obj)
|
||||||
|
return !(wr.get_ref() and str(obj) != '[Deleted Object]')
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Pretty self explanitory.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func is_not_freed(obj):
|
||||||
|
return !is_freed(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Checks if the passed in object is a GUT Double or Partial Double.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func is_double(obj):
|
||||||
|
var to_return = false
|
||||||
|
if(typeof(obj) == TYPE_OBJECT and is_instance_valid(obj)):
|
||||||
|
to_return = obj.has_method('__gut_instance_from_id')
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Returns an array of values by calling get(property) on each element in source
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func extract_property_from_array(source, property):
|
||||||
|
var to_return = []
|
||||||
|
for i in (source.size()):
|
||||||
|
to_return.append(source[i].get(property))
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# true if file exists, false if not.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func file_exists(path):
|
||||||
|
return _file_checker.file_exists(path)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Write a file.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func write_file(path, content):
|
||||||
|
var f = File.new()
|
||||||
|
var result = f.open(path, f.WRITE)
|
||||||
|
if(result == OK):
|
||||||
|
f.store_string(content)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# true if what is passed in is null or an empty string.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func is_null_or_empty(text):
|
||||||
|
return text == null or text == ''
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Get the name of a native class or null if the object passed in is not a
|
||||||
|
# native class.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func get_native_class_name(thing):
|
||||||
|
var to_return = null
|
||||||
|
if(is_native_class(thing)):
|
||||||
|
to_return = thing.new().get_class()
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Checks an object to see if it is a GDScriptNativeClass
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func is_native_class(thing):
|
||||||
|
var it_is = false
|
||||||
|
if(typeof(thing) == TYPE_OBJECT):
|
||||||
|
it_is = str(thing).begins_with("[GDScriptNativeClass:")
|
||||||
|
return it_is
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Returns the text of a file or an empty string if the file could not be opened.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func get_file_as_text(path):
|
||||||
|
var to_return = ''
|
||||||
|
var f = File.new()
|
||||||
|
var result = f.open(path, f.READ)
|
||||||
|
if(result == OK):
|
||||||
|
to_return = f.get_as_text()
|
||||||
|
f.close()
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Loops through an array of things and calls a method or checks a property on
|
||||||
|
# each element until it finds the returned value. The item in the array is
|
||||||
|
# returned or null if it is not found.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
func search_array(ar, prop_method, value):
|
||||||
|
var found = false
|
||||||
|
var idx = 0
|
||||||
|
|
||||||
|
while(idx < ar.size() and !found):
|
||||||
|
var item = ar[idx]
|
||||||
|
if(item.get(prop_method) != null):
|
||||||
|
if(item.get(prop_method) == value):
|
||||||
|
found = true
|
||||||
|
elif(item.has_method(prop_method)):
|
||||||
|
if(item.call(prop_method) == value):
|
||||||
|
found = true
|
||||||
|
|
||||||
|
if(!found):
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
if(found):
|
||||||
|
return ar[idx]
|
||||||
|
else:
|
||||||
|
return null
|
7
test_proj/default_env.tres
Normal file
7
test_proj/default_env.tres
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[gd_resource type="Environment" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[sub_resource type="ProceduralSky" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
background_mode = 2
|
||||||
|
background_sky = SubResource( 1 )
|
BIN
test_proj/icon.png
Normal file
BIN
test_proj/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
34
test_proj/icon.png.import
Normal file
34
test_proj/icon.png.import
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="StreamTexture"
|
||||||
|
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.png"
|
||||||
|
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_mode=0
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
flags/repeat=0
|
||||||
|
flags/filter=true
|
||||||
|
flags/mipmaps=false
|
||||||
|
flags/anisotropic=false
|
||||||
|
flags/srgb=2
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/HDR_as_SRGB=false
|
||||||
|
process/invert_color=false
|
||||||
|
stream=false
|
||||||
|
size_limit=0
|
||||||
|
detect_3d=true
|
||||||
|
svg/scale=1.0
|
28
test_proj/project.godot
Normal file
28
test_proj/project.godot
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=4
|
||||||
|
|
||||||
|
_global_script_classes=[ ]
|
||||||
|
_global_script_class_icons={
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="test_proj"
|
||||||
|
run/main_scene="res://Gut.tscn"
|
||||||
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PoolStringArray( "gut" )
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
environment/default_environment="res://default_env.tres"
|
4
test_proj/tests/test_test.gd
Normal file
4
test_proj/tests/test_test.gd
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
extends "res://addons/gut/test.gd"
|
||||||
|
|
||||||
|
func test_test():
|
||||||
|
assert_eq(1,1)
|
Loading…
Reference in New Issue
Block a user