1
0
mirror of https://github.com/gryf/fs-uae-wrapper.git synced 2026-02-02 22:25:47 +01:00

58 Commits
0.3 ... 0.10.5

Author SHA1 Message Date
59bd1b6029 Make tempfile prefixed with fs-uae-wrapper string.
Also, included test requirements into tox file, and fixed docs.
2025-09-25 18:50:28 +02:00
f5e6471555 Removed license classifier in favor of SPDX entry. 2025-04-18 16:05:37 +02:00
4c61c3d7ea Fix user provided whdload options 2024-09-14 17:00:28 +02:00
7e3d68624f Fixed packages find in pyproject 2024-09-14 15:59:25 +02:00
f311605019 Take version from git tag. 2024-09-14 11:49:44 +02:00
418e480fb5 Fixes for readme 2024-09-14 11:34:33 +02:00
118d758ec7 Trailing space 2024-09-14 10:57:50 +02:00
3b597e34ee Fix some lint errors and warnings 2024-09-14 10:53:58 +02:00
7b40974779 Fixed imports order 2024-09-14 09:22:41 +02:00
b4ab8ac4f5 Change GH action icon location 2024-09-13 21:29:54 +02:00
8d8d38d5c0 Move from travis to gh actions 2024-09-13 21:27:40 +02:00
bd0aa3dee4 Changed name for the tests workflow 2024-09-13 21:25:00 +02:00
d2a9f39fd9 Create python-app.yml 2024-09-13 21:02:58 +02:00
f1f64cf4d4 Added WHDLoad support 2024-09-13 18:18:35 +02:00
a5606272cd Fix unit tests 2024-09-13 18:16:45 +02:00
463f6ed705 Fixed deduction of archive name.
When in different directory, the archive name and config need to be
treated differently. This commit unify behavior for both cases.

Also, changing a bit logging format and added some more debugging
messages.
2024-09-12 20:29:34 +02:00
148d28dac2 Make archive name and archiver for savestate optional. 2024-09-11 21:23:49 +02:00
60139d1728 Move to pyproject. 2024-09-11 19:53:52 +02:00
68b1f2a787 Correct homepage in setup.cfg 2022-10-02 14:57:38 +02:00
75d2cff96c Drop Python2 support. 2022-09-02 19:01:56 +02:00
114984cbee Go with setup.cfg instead of setup.py. 2022-09-02 18:47:17 +02:00
b015e443eb Fix tests for file path calculations 2021-05-02 09:36:14 +02:00
874213aef9 Prevent from nonexistent paths in config.
In case of defining paths in config, which wrapper is trying to
normalize, sometimes it may happen, that paths can be either local or
remote - depending on the intention, i.e. one can add:

cdrom_drive_0: foo.iso

where foo can be either an unpacked file or just some companion image,
which exists within config file.

In this patch calculated path is checked against file existence, and if
it doesn't exists, original value is preserved.
2021-03-20 13:12:58 +01:00
1559591417 Changed python3 interpreter to default.
Currently, as a python3 interpreter, specific version has been chosen.
As fs-uae-wrapper doesn't use any special features from latest version
of the python3, it doesn't make sense to have fixed version of it. Just
let operating system pick right python 3 interpreter.
2019-10-06 18:09:32 +02:00
193e487bb9 Fix download link and typo in readme 2017-06-19 19:55:02 +02:00
eb9012f3fa Readme update 2017-01-12 21:09:12 +01:00
efbb5f6f05 Readme update 2017-01-09 06:41:16 +01:00
00e3cf9801 Introduced new configuration variable $WRAPPER
Calculation of file path may be performed in two different places -
first is the original location of the config file, second is on copied
one. Adding new option WRAPPER will take the same role as $CONFIG,
except it will be only parsed on copied configuration file.

An alternative would be to check existence in both places, but I'd like
to have it explicit.
2017-01-08 19:40:27 +01:00
388a8cc835 Fix for interpolate_variables function 2017-01-08 15:38:59 +01:00
83185011aa Fix for config options containing HOME variable 2017-01-08 15:37:29 +01:00
152446abbe Use logging for displaying messages
Also added debug logs
2017-01-08 13:58:47 +01:00
a918e4c9ff Added logging module 2017-01-08 13:17:41 +01:00
7931022777 Additional tests for util module 2017-01-08 12:35:51 +01:00
1a67b355ae Removed unused run function from savestate wrapper 2017-01-08 12:01:25 +01:00
6a026ecff1 Fix for typo in attribute name 2017-01-08 11:59:43 +01:00
bd0e3ea56a Added more tests for file_archive module 2017-01-08 11:56:40 +01:00
8a6ddda7c8 Make plain wrapper module use base.Base class 2017-01-08 11:21:43 +01:00
a14871c52f Get rid of run function for every wrapper module 2017-01-08 11:20:33 +01:00
8528d28a42 Get rid of unneeded _run_emulator parameter 2017-01-08 11:17:37 +01:00
ab4042f880 Refactor wrapper modules to have a main class with the same name 2017-01-07 13:13:18 +01:00
8892940339 Normalize paths for config options
Removing _kickstart_option in favor of _normalize_options method to
detect and replace relative paths with absolute ones. Dictionary with
changed options lands as a commandline switches for fs-uae during
execution.
2017-01-07 13:13:06 +01:00
853dca385e Readme update 2017-01-07 09:55:22 +01:00
334b56bad3 Version bump 2017-01-07 09:45:37 +01:00
6d84fc4b8a Make wrapper_archiver option mandatory in savestare module 2017-01-07 09:43:53 +01:00
c5ce27e637 Added savestate wrapper module 2017-01-07 09:31:59 +01:00
ae6e00ea1c Added gitignore 2017-01-07 09:29:18 +01:00
f1fb43ca64 Extract new base class for archive wrapper module 2017-01-07 09:10:03 +01:00
77cf10cee7 Fixed issue with compressing files with tar.*
This changeset fixes tar behavior on creating archive, like:

  tar cf foo.tar .

which include also archive file itself, so that tar reports an error
`file changed as we read it` during appending archive file to itself.

This changest is fixing that by replacing dot with list of items to be
added to the archive (similar as in RarArchive.create).
2017-01-06 16:53:49 +01:00
250b1c4c2c Version bump 2017-01-03 20:07:01 +01:00
0cbe6fc9d0 Added wrapper_save_state option 2017-01-03 19:34:42 +01:00
0b831e5b10 Setting assets paths in base class 2017-01-03 18:49:18 +01:00
1d35436dee Fixed tests for wrapper modules
Exchanged os.rename with shutil.move since there was a problem with
moving files between different filesystems.
2017-01-03 06:01:30 +01:00
db7b8e347a Version bump 2017-01-02 20:19:30 +01:00
994768806c Parametrize extract and create methods for Archive base class 2017-01-02 20:17:13 +01:00
19acb789b6 Added wrapper_archiver option
To give user a possibility to choose what archiver he can use another
options was introduced for cd32 and archive wrapper modules. This option
will indicate what archiver should be used for compressing the save
state directories.
2017-01-02 20:08:50 +01:00
5f98e9b794 Moved save state out of _make_archive method
Now _save_save method will be called from run() method.
2017-01-02 19:30:09 +01:00
3906e4c80b Moved which function to another module 2017-01-02 19:27:49 +01:00
38a2322e98 Fix for file_archive tests 2017-01-02 16:05:18 +01:00
31 changed files with 2015 additions and 754 deletions

33
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Test
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest tox coverage pytest-cov pytest-pep8
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
tox

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*.pyc
*.egg-info
.cache
.coverage
.tox
MANIFEST
build
dist

View File

@@ -1,8 +0,0 @@
language: python
env:
- TOXENV=py27
- TOXENV=py27-flake8
- TOXENV=py34
- TOXENV=py34-flake8
install: pip install tox
script: tox

View File

@@ -2,8 +2,8 @@
FS-UAE Wrapper
==============
.. image:: https://travis-ci.org/gryf/fs-uae-wrapper.svg?branch=master
:target: https://travis-ci.org/gryf/fs-uae-wrapper
.. image:: https://github.com/gryf/fs-uae-wrapper/workflows/Test/badge.svg
:target: https://github.com/gryf/fs-uae-wrapper/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster
.. image:: https://img.shields.io/pypi/v/fs-uae-wrapper.svg
:target: https://pypi.python.org/pypi/fs-uae-wrapper
@@ -12,7 +12,7 @@ This little utility is a wrapper on great FS-UAE_ emulator, to perform some
actions, like uncompressing archived files (CD ROMs images, filesystems),
launch the emulator and archive emulator save state.
As an example, if there is a collection of CD³² game files and one want to
As an example, if there is a collection of CD³² game files and you want to
provide configuration for each game, but want to keep ISO images with
corresponding files in archive (due to size of those images) than FS-UAE
Wrapper is a way to achieve this.
@@ -25,11 +25,10 @@ preferably in a archive file vs a bunch of files.
Requirements
============
- Python (2 or 3)
- Python 3
- `fs-uae`_ (obviously :)
Also, there should be archivers programs available for compress/decompress
archives. Fs-uae-wrapper supports several types of archives:
Fs-uae-wrapper supports several types of archives:
- `7z`_
- `lha`_
@@ -43,25 +42,35 @@ archives. Fs-uae-wrapper supports several types of archives:
- `zip`_
There are several archive types which are supported, ranging from tar
(compressed with gzip, bzip2 and xz), 7z, rar, zip. lha and lzx. All of those
formats should have corresponding decompressors available in the system,
otherwise archive extraction will fail.
All of those formats should have corresponding software available in the
system, otherwise archive extraction/compression will fail.
Installation
============
Just perform (preferably in ``virtualenv``) a command:
FS-UAE Wrapper is available on `CheeseShop`_ (or python package index if you
will). To install it, you can simply invoke (preferably in ``virtualenv``) a
command:
.. code:: shell-session
$ pip install fs-uae-wrapper
Note, that if ``virtualenv`` was used, there is no need for activating it every
time, since if invoke wrapper like this:
.. code:: shell-session
$ /path/to/virtualenv/bin/fs-uae-wrapper
you should be able run the wrapper properly. *Tested only on Linux :)*.
Usage
=====
After installation you should be able to access new command
``fs-uae-wrapper``, and its invocation is identical like ``fs-uae`` binary:
After installation you should be able to access new command ``fs-uae-wrapper``
(or use the full path to the ``virtualenv`` like on the section above), and it's
invocation is identical like ``fs-uae`` binary:
.. code:: shell-session
@@ -73,9 +82,9 @@ directly in fs-uae configuration or passed as an option:
.. code:: ini
[config]
...
# ...
wrapper = cd32
...
# ...
or
@@ -89,23 +98,46 @@ specific module will be loaded and depending on the module, there would be
performed specific tasks before ``fs-uae`` is launched and after it.
Assumption is, that configuration file are written in portable way, so the are
saved as `relative configuration file`_ (hence the name ``Config.fs-uae``, even
if the are named differently. If certain wrapper is specified, it will create
temporary directory and place the configuration file there as
saved as `relative configuration file`_ (hence the name ``Config.fs-uae``),
even if the are named differently. If certain wrapper is specified, it will
create temporary directory and place the configuration file there as
``Config.fs-uae``.
If no ``wrapper`` option would be passed either as an config option or
command line argument, all command line options will be passed to the fs-uae
executable as-is.
Note, that you can also pass all *wrapper* options via commandline in the very
same way as you can pass config options to `fs-uae`, so you don't have to
modify original configuration if you don't want to.
There is also new config variable introduced: ``$WRAPPER`` which have the same
role as ``$CONFIG``, but apply for copied config. For instance - in module
archive there are filesystem extracted to new location - to access this
filesystem relatively to the copied configuration file it is enough to provide
a config option:
.. code:: ini
[config]
wrapper = archive
# ...
hard_drive_0 = $WRAPPER/my_hardrive
which means, that we are expecting to have system files on ``my_hardrive`` in
directory, where configuration will be copied.
Modules
=======
Currently, three wrapper modules are available:
Currently, couple of wrapper modules are available:
- plain
- cd32
- archive
- savestate
- whdload
plain
-----
@@ -126,18 +158,25 @@ Options used:
* ``wrapper`` (required) with ``cd32`` as an value
* ``wrapper_archive`` (required) path to the archive with CD32 iso/cue/wav
* ``wrapper_archiver`` (optional) archiver to use for storage save state -
default ``7z``.
* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical
message during extracting files
* ``wrapper_save_state`` (optional) if set to "1", will load/archive save state
directory, defined as ``$WRAPPER/[save-state-dir-name]`` using provided
``wrapper_archiver`` archiver. If this option is enabled,
``wrapper_archiver`` will be required.
Let's see some sample config for a game, which is saved as
``ChaosEngine.fs-uae``:
Module ``cd32`` is used for running ``fs-uae`` with compressed CD images. For
better understanding how it works, let's go through solid example. Here is an
fragment of configuration file is saved as ``ChaosEngine.fs-uae``:
.. code:: ini
:number-lines:
[config]
wrapper = cd32
wrapper_archive = ChaosEngine.7z
wrapper_archiver = zip
wrapper_gui_msg = 1
amiga_model = CD32
@@ -145,12 +184,13 @@ Let's see some sample config for a game, which is saved as
cdrom_drive_0 = Chaos Engine, The (1994)(Renegade)(M4)[!][CDD3445].cue
save_states_dir = $CONFIG/fs-uae-save/
save_states_dir = $WRAPPER/fs-uae-save/
joystick_port_1_mode = cd32 gamepad
platform = cd32
# ...
Next, the invocation of the wrapper would be as follows:
Command line invocation of the wrapper would be as follows:
.. code:: shell-session
@@ -158,20 +198,20 @@ Next, the invocation of the wrapper would be as follows:
Now, there several thing will happen:
- Config file will be read, and wrapper module will be find (because we already
put it on line 2)
- Config file will be read, and wrapper module will be found
- New temporary directory will be created
- Archive with game assists will be extracted in that directory
- Archive with game assets will be extracted in that directory
- Configuration file will be copied into that directory, and renamed to
``Config.fs-uae``
- If there is saved state, it also would be extracted there
- If ``wrapper_save_state`` is set, and there is saved state archive, it also
would be extracted there
- ``fs-uae`` will be launched inside that directory
Next, after ``fs-uae`` quit, there will:
- Create archive containing save state with name like the configuration file
with additional ``_save`` suffix. In this example it would be
``ChaosEngine_save.7z``.
- Optionally create archive containing save state with name like the
configuration file with additional ``_save`` suffix. In this example it would
be ``ChaosEngine_save.7z``.
- Wipe out temporary directory
archive
@@ -179,26 +219,41 @@ archive
Options used:
* ``wrapper`` (required) with ``cd32`` as an value
* ``wrapper_archive`` (required) path to the archive with assets (usually means
whole system directories, floppies or hd images)
* ``wrapper`` (required) with ``archive`` as an value
* ``wrapper_archive`` (optional) path to the archive with assets (usually means
whole system directories, floppies or hard disk images), defaults to same
name as configuration file with some detected archive extension. Note, that
name is case sensitive
* ``wrapper_archiver`` (optional) archiver to use for storage save state -
default ``7z``.
* ``wrapper_gui_msg`` (optional) if set to "1", will display a graphical
message during extracting files
* ``wrapper_persist_data`` (optional) if set to "1", will compress (possibly
changed) data, replacing original archive
* ``wrapper_save_state`` (optional) if set to "1", will archive save state
directory, defined as ``$WRAPPER/[save-state-dir-name]`` using provided
``wrapper_archiver`` archiver. If this option is enabled,
``wrapper_archiver`` will be required.
This module is quite useful in two use cases. First is a usual work with
Workbench, where there is a need to keep changes of filesystem. Second is the
opposite - if there is a need to test some software, but not necessary keep it
in a Workbench, than it will act as a temporary copy of the system, so that
next time fs-uae will be run, there will be no files of tested software
cluttering around.
Example configuration:
.. code:: ini
:number-lines:
[config]
wrapper = archive
wrapper_archive = Workbench_3.1.tar.bz2
wrapper_archiver = lha
wrapper_gui_msg = 1
wrapper_persist_data = 1
...
wrapper_save_state = 1
# ...
And execution is as usual:
@@ -210,20 +265,261 @@ This module will do several steps (similar as with ``cd32`` wrapper):
- create temporary directory
- extract provided in configuration archive
- extract save state (if exists)
- extract save state (if ``wrapper_save_state`` is set to ``1`` and archive
with save exists)
- copy configuration under name ``Config.fs-uae``
- run the fs-uae emulator
- create archive with save state (if save state directory place is *not* a
global one)
- optionally create archive with save state (if save state directory place is
*not* a global one)
- optionally create new archive under the same name as the original one and
replace it with original one.
This module is quite useful in two use cases. First is a usual work with
Workbench, where there is a need to keep changes of filesystem. Second is the
opposite - if there is a need to test some software, but not necessary keep it
in a Workbench, than it will act as a temporary copy of the system, so that
next time fs-uae will be run, there will be no files of tested software
cluttering around.
savestate
---------
Options used:
* ``wrapper`` (required) with ``archive`` as an value
* ``wrapper_archiver`` (optional) archiver to use for storage save state -
default ``7z``.
This module is primarily used to run emulator with read only media attached
(like images of floppies or uncompressed CD-ROMs) and its purpose is to
preserve save state which will be created as an archive alongside with original
configuration file in selected archive format. Note, that there is required to
provide ``wrapper_archiver``, since option ``wrapper_save_state`` is implicitly
set to value ``1`` in this module.
Example configuration:
.. code:: ini
[config]
wrapper = savestate
wrapper_archiver = 7z
# ...
And execution is as usual:
.. code:: shell-session
$ fs-uae-wrapper Sanity-Arte.fs-uae
The steps would be as follows:
- create temporary directory
- extract save state (if ``wrapper_save_state`` is set to ``1`` and archive
with save exists)
- copy configuration under name ``Config.fs-uae``
- run the fs-uae emulator
- optionally create archive with save state (if save state directory place is
*not* a global one)
whdload
-------
Options used:
* ``wrapper`` (required) with ``whdload`` as an value
* ``wrapper_whdload_base`` (required) path to the whdload base system. Usually
it's minimal system containing at least whdload executables in C, and config
in S. Read on below for further details.
* ``wrapper_whdload_options`` (optional) this option will replace the line in
``s:whdload-startup`` with specific ``whdload`` options for certain slave.
For reference look at WHDLoad documentation and/or on ``s:WHDLoad.prefs``.
Note, that ``Slave=`` option must not be used.
* ``wrapper_archive`` (optional) path to the whdload archive, defaults to same
name as configuration file with some detected archive extension. Note, that
name is case sensitive
This module is solely used with whdload distributed games (not just whdload
slave files, but whole games, which can be found on several places on the
internet).
Base image
~~~~~~~~~~
To make it work, first the absolute minimal image need to contain following
structure:
.. code::
.
├── C
│   ├── DIC
│   ├── Execute
│   ├── Patcher
│   ├── RawDIC
│   ├── SetPatch
│   ├── WHDLoad
│   └── WHDLoadCD32
└── S
├── startup-sequence
└── WHDLoad.prefs
where the minimum dependences are:
- ``Excecute`` from your copy of Workbench
- `WHDLoad`_ 18.9
- `SetPatch`_ 43.6
and the ``S/startup-sequence`` should at least contain:
.. code::
setpatch QUIET
IF EXISTS S:whdload-startup
Execute S:whdload-startup
EndIF
To leverage more pleasant UX, additionally those bits should be installed (or -
copied into base image filesystem):
- ``Assign`` and whatever commands you'll be use in scripts from your copy of
Workbench
- `uaequit`_ - this will allow to quit emulator, after quiting game
- `kgiconload`_ - tool for reading icon and executing *default tool* with
optionally defined tool types as parameters (in this case: WHDLoad)
- `SKick`_ optionally - for kickstart relocations. Also images of corresponding
kickstart ROM images will be needed.
and then ``s/startup-sequence`` might looks a follows:
.. code::
Assign >NIL: ENV: RAM:
Assign >NIL: T: RAM:
setpatch QUIET
IF EXISTS S:whdload-startup
Execute S:whdload-startup
EndIF
C:UAEquit
Creating base image archive
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now, the tree for the minimal image could look like that:
.. code::
.
├── C
│   ├── Assign
│   ├── DIC
│   ├── Execute
│   ├── kgiconload
│   ├── Patcher
│   ├── RawDIC
│   ├── SetPatch
│   ├── UAEquit
│   ├── WHDLoad
│   └── WHDLoadCD32
└── S
├── startup-sequence
└── WHDLoad.prefs
to use relocation tables you'll need to place ``Kickstarts`` drawer into Devs
drawer. Also keep in mind, that corresponding kickstart rom images need to be
placed there as well, otherwise it may or may not work. Structure looks like
this:
.. code::
.
├── C
│   ├── Assign
│   ├── …
│   └── WHDLoadCD32
├── Devs
│   └── Kickstarts
│   ├── 39115_ROMKick.PAT
│   ├── 39115_ROMKick.RTB
│   ├── …
│   ├── kick40068.A4000.PAT
│   └── kick40068.A4000.RTB
└── S
├── startup-sequence
└── WHDLoad.prefs
Important: You'll need to prepare archive with base OS without top directory,
i.e. suppose you have prepared all the files in ``/tmp/baseos``:
.. code:: shell-session
$ pwd
/tmp
$ cd baseos
$ pwd
/tmp/basos
$ ls
C S
$ zip -r /tmp/base.zip .
adding: C/ (stored 0%)
adding: C/Assign (deflated 31%)
adding: C/WHDLoadCD32 (deflated 26%)
adding: C/RawDIC (deflated 46%)
adding: C/UAEquit (deflated 39%)
adding: C/Execute (deflated 42%)
adding: C/Patcher (deflated 56%)
adding: C/DIC (deflated 33%)
adding: C/kgiconload (deflated 49%)
adding: C/SetPatch (deflated 39%)
adding: C/WHDLoad (deflated 23%)
adding: S/ (stored 0%)
adding: S/startup-sequence (deflated 36%)
adding: S/WHDLoad.prefs (deflated 51%)
You can do it with other archivers as well, like 7z: ``7z a /tmp/base.7z .`` or
tar with different compressions: ``tar Jcf /tmp/base.tar.xz .``, ``tar zcf
/tmp/base.tgz .``, ``tar jcf /tmp/base.tar.bz2 .``. It should work with all
mentioned at the beginning of this document archivers.
Starting point is in ``S/startup-sequence`` file, where eventually
``S/whdload-startup`` is executed, which will be created by fs-uae-warpper
before execution by fs-uae.
Configuration
~~~~~~~~~~~~~
Now, to use whdload module with any of the WHDLoad game, you'll need to prepare
configuration for the wrapper.
Example configuration:
.. code:: ini
[config]
wrapper = whdload
wrapper_whdload_base = $CONFIG/whdload_base.7z
# ...
And execution is as usual:
.. code:: shell-session
$ fs-uae-wrapper ChaosEngine_v1.2_0106.fs-uae
Now, similar to the archive module, it will create temporary directory, unpack
base image there, unpack WHDLoad game archive, search for slave file, and
prepare ``s:whdload-startup``, and finally pass all the configuration to
fs-uae.
Limitations
===========
There is one limitation when using save ``wrapper_save_state`` option. In case
of floppies it should work without any issues, although save state for running
Workbench or WHDLoad games may or may not work. In the past there was an issue
with `fs-uae`_ where saving state was causing data corruption on the emulated
system. Use it with caution!
License
=======
@@ -238,3 +534,9 @@ This work is licensed on 3-clause BSD license. See LICENSE file for details.
.. _lzx: http://aminet.net/package/misc/unix/unlzx.c.readme
.. _tar: https://www.gnu.org/software/tar/
.. _zip: http://www.info-zip.org
.. _CheeseShop: https://pypi.python.org/pypi/fs-/fs-uae-wrapperuae-wrapper
.. _WHDLoad: https://www.whdload.de
.. _uaequit: https://aminet.net/package/misc/emu/UAEquit
.. _SKick: https://aminet.net/package/util/boot/skick346
.. _SetPatch: https://aminet.net/package/util/boot/SetPatch_43.6b
.. _kgiconload: https://eab.abime.net/showpost.php?p=733614&postcount=92

View File

@@ -6,41 +6,31 @@ the temporary one.
"""
import os
import shutil
import sys
from fs_uae_wrapper import base
from fs_uae_wrapper import utils
from fs_uae_wrapper import base, utils
class Archive(base.Base):
class Wrapper(base.ArchiveBase):
"""
Class for performing extracting archive, copying emulator files, and
cleaning it back again
"""
def __init__(self, conf_file, fsuae_options, configuration):
super(Archive, self).__init__(conf_file, fsuae_options, configuration)
super(Wrapper, self).__init__(conf_file, fsuae_options, configuration)
self.archive_type = None
def run(self):
"""
Main function which accepts configuration file for FS-UAE
It will do as follows:
- set needed full path for asset files
- extract archive file
- copy configuration
- run the emulation
- optionally make archive save state
Params:
conf_file: a relative path to provided configuration file
fsuae_options: is an CmdOption object created out of command line
parameters
configuration: is config dictionary created out of config file
"""
if not super(Archive, self).run():
if not super(Wrapper, self).run():
return False
self._set_assets_paths()
if not self._extract():
return False
@@ -49,29 +39,15 @@ class Archive(base.Base):
if not self._copy_conf():
return False
kick_opts = self._kickstart_option()
if kick_opts:
self.fsuae_options.update(kick_opts)
if not self._run_emulator(self.fsuae_options.list()):
if not self._run_emulator():
return False
if self._get_saves_dir():
if not self._save_save():
return False
return self._make_archive()
def _validate_options(self):
validation_result = super(Archive, self)._validate_options()
if not super(Archive, self)._validate_options():
validation_result = False
if 'wrapper_archive' not in self.all_options:
sys.stderr.write("Configuration lacks of required "
"`wrapper_archive' option.\n")
validation_result = False
return validation_result
def _make_archive(self):
"""
Produce archive and save it back. Than remove old one.
@@ -79,14 +55,10 @@ class Archive(base.Base):
if self.all_options.get('wrapper_persist_data', '0') != '1':
return True
saves = self._get_saves_dir()
if saves:
if not self._save_save():
return False
curdir = os.path.abspath('.')
os.chdir(self.dir)
saves = self._get_saves_dir()
if saves:
shutil.rmtree(saves)
os.unlink('Config.fs-uae')
@@ -97,16 +69,6 @@ class Archive(base.Base):
if not utils.create_archive(arch, title):
return False
os.rename(arch, self.arch_filepath)
shutil.move(arch, self.arch_filepath)
os.chdir(curdir)
return True
def run(config_file, fsuae_options, configuration):
"""Run fs-uae with provided config file and options"""
runner = Archive(config_file, fsuae_options, configuration)
try:
return runner.run()
finally:
runner.clean()

View File

@@ -1,12 +1,12 @@
"""
Base class for all wrapper modules
"""
import logging
import os
import sys
import shutil
import tempfile
from fs_uae_wrapper import utils
from fs_uae_wrapper import path, utils
class Base(object):
@@ -28,7 +28,6 @@ class Base(object):
fsuae_options)
self.dir = None
self.save_filename = None
self.arch_filepath = None
def run(self):
"""
@@ -41,10 +40,13 @@ class Base(object):
- run the emulation
- archive save state
"""
logging.debug("run")
if not self._validate_options():
return False
self.dir = tempfile.mkdtemp()
self.dir = tempfile.mkdtemp(prefix='fs-uae-wrapper-')
self._normalize_options()
self._set_assets_paths()
return True
@@ -54,33 +56,6 @@ class Base(object):
shutil.rmtree(self.dir)
return
def _kickstart_option(self):
"""
This is kind of hack - since we potentially can have a relative path
to kickstart directory, there is a need for getting this option from
configuration files (which unfortunately can be spanned all over the
different places, see https://fs-uae.net/configuration-files) and
check whether or not one of 'kickstarts_dir', 'kickstart_file' or
'kickstart_ext_file' options are set. In either case if one of those
options are set and are relative, they should be set to absolute path,
so that kickstart files can be found by relocated configuration file.
"""
conf = utils.get_config(self.conf_file)
kick = {}
for key in ('kickstart_file', 'kickstart_ext_file', 'kickstarts_dir'):
val = conf.get(key)
if val:
if not os.path.isabs(val):
val = utils.interpolate_variables(val, self.conf_file)
kick[key] = os.path.abspath(val)
else:
kick[key] = val
return kick
def _set_assets_paths(self):
"""
Set full paths for archive file (without extension) and for save state
@@ -90,13 +65,11 @@ class Base(object):
conf_base = os.path.basename(self.conf_file)
conf_base = os.path.splitext(conf_base)[0]
arch = self.all_options['wrapper_archive']
if os.path.isabs(arch):
self.arch_filepath = arch
else:
self.arch_filepath = os.path.join(conf_abs_dir, arch)
# set optional save_state
self.save_filename = os.path.join(conf_abs_dir, conf_base + '_save.7z')
arch_ext = utils.get_arch_ext(self.all_options.get('wrapper_archiver'))
if arch_ext:
self.save_filename = os.path.join(conf_abs_dir, conf_base +
'_save' + arch_ext)
def _copy_conf(self):
"""copy provided configuration as Config.fs-uae"""
@@ -105,21 +78,11 @@ class Base(object):
os.path.join(self.dir, 'Config.fs-uae'))
return True
def _extract(self):
"""Extract archive to temp dir"""
title = self._get_title()
def _run_emulator(self):
"""execute fs-uae"""
curdir = os.path.abspath('.')
os.chdir(self.dir)
result = utils.extract_archive(self.arch_filepath, title)
os.chdir(curdir)
return result
def _run_emulator(self, fsuae_options):
"""execute fs-uae in provided directory"""
curdir = os.path.abspath('.')
os.chdir(self.dir)
utils.run_command(['fs-uae'] + fsuae_options)
utils.run_command(['fs-uae', *self.fsuae_options.list()])
os.chdir(curdir)
return True
@@ -140,6 +103,10 @@ class Base(object):
"""
Get the saves from emulator and store it where configuration is placed
"""
if self.all_options.get('wrapper_save_state', '0') != '1':
return True
os.chdir(self.dir)
save_path = self._get_saves_dir()
if not save_path:
return True
@@ -147,39 +114,47 @@ class Base(object):
if os.path.exists(self.save_filename):
os.unlink(self.save_filename)
if not utils.run_command(['7z', 'a', self.save_filename, save_path]):
sys.stderr.write('Error: archiving save state failed\n')
curdir = os.path.abspath('.')
if not utils.create_archive(self.save_filename, '', [save_path]):
logging.error('Error: archiving save state failed.')
os.chdir(curdir)
return False
os.chdir(curdir)
return True
def _load_save(self):
"""
Put the saves (if exists) to the temp directory.
"""
if self.all_options.get('wrapper_save_state', '0') != '1':
return True
if not os.path.exists(self.save_filename):
return True
curdir = os.path.abspath('.')
os.chdir(self.dir)
utils.run_command(['7z', 'x', self.save_filename])
utils.extract_archive(self.save_filename)
os.chdir(curdir)
return True
def _get_saves_dir(self):
"""
Return full path to save state directory or None in cases:
- there is no save state dir set relative to config file
Return path to save state directory or None in cases:
- there is no save state dir set relative to copied config file
- save state dir is set globally
- save state dir is set relative to the config file
- save state dir doesn't exists
Note, that returned path is relative not absolute
"""
if not self.all_options.get('save_states_dir'):
return None
if self.all_options['save_states_dir'].startswith('$CONFIG') and \
if self.all_options['save_states_dir'].startswith('$WRAPPER') and \
'..' not in self.all_options['save_states_dir']:
save = self.all_options['save_states_dir'].replace('$CONFIG/', '')
save = self.all_options['save_states_dir'].replace('$WRAPPER/', '')
else:
return None
@@ -187,12 +162,167 @@ class Base(object):
if not os.path.exists(save_path) or not os.path.isdir(save_path):
return None
return save_path
if save.endswith('/'):
save = save[:-1]
return save
def _normalize_options(self):
"""
Search and replace values for options which starts with $CONFIG with
absolute path for all options.
Configuration file will be placed in new directory, therefore it is
needed to calculate new paths so that emulator can find assets.
"""
logging.debug("_normalize_options")
options = ['wrapper_archive', 'wrapper_whdload_base',
'accelerator_rom', 'base_dir', 'cdrom_drive_0',
'cdroms_dir', 'controllers_dir', 'cpuboard_flash_ext_file',
'cpuboard_flash_file', 'floppies_dir',
'floppy_overlays_dir', 'fmv_rom', 'graphics_card_rom',
'hard_drives_dir', 'kickstart_file', 'kickstarts_dir',
'logs_dir', 'save_states_dir', 'screenshots_output_dir']
for num in range(20):
options.append('cdrom_image_%d' % num)
options.append('floppy_image_%d' % num)
for num in range(4):
options.append('floppy_drive_%d' % num)
for num in range(10):
options.append('hard_drive_%d' % num)
changed_options = {}
for key, val in utils.get_config(self.conf_file).items():
if key not in options:
continue
if val.startswith('/'):
continue
if val.startswith('~'):
continue
if val.startswith('$HOME'):
continue
if val.startswith('$WRAPPER'):
changed_options[key] = val.replace('$WRAPPER', self.dir)
continue
if val.startswith('$CONFIG'):
abspath = utils.interpolate_variables(val, self.conf_file)
changed_options[key] = abspath
logging.info("%s: %s => %s", key, val, abspath)
continue
_val = os.path.abspath(val)
if os.path.exists(_val):
changed_options[key] = _val
else:
changed_options[key] = val
self.fsuae_options.update(changed_options)
def _validate_options(self):
"""Validate mandatory options"""
if 'wrapper' not in self.all_options:
sys.stderr.write("Configuration lacks of required "
"`wrapper' option.\n")
logging.error("Configuration lacks of required `wrapper' option.")
return False
if self.all_options.get('wrapper_save_state', '0') == '0':
return True
if 'wrapper_archiver' not in self.all_options:
logging.warning("Configuration lacks of optional "
"`wrapper_archiver' option, fall back to 7z")
self.all_options['wrapper_archiver'] = "7z"
if not path.which(self.all_options['wrapper_archiver']):
logging.error("Cannot find archiver `%s'.",
self.all_options['wrapper_archiver'])
return False
return True
class ArchiveBase(Base):
"""
Base class for archive based wrapper modules
"""
def __init__(self, conf_file, fsuae_options, configuration):
"""
Params:
conf_file: a relative path to provided configuration file
fsuae_options: is an CmdOption object created out of command line
parameters
configuration: is config dictionary created out of config file
"""
super(ArchiveBase, self).__init__(conf_file, fsuae_options,
configuration)
self.arch_filepath = None
def _set_assets_paths(self):
"""
Set full paths for archive file (without extension) and for save state
archive file
"""
super(ArchiveBase, self)._set_assets_paths()
conf_abs_dir = os.path.dirname(os.path.abspath(self.conf_file))
arch = self.all_options.get('wrapper_archive')
if arch:
if os.path.isabs(arch):
self.arch_filepath = arch
else:
self.arch_filepath = os.path.join(conf_abs_dir, arch)
def _extract(self):
"""Extract archive to temp dir"""
logging.debug("_extract")
title = self._get_title()
curdir = os.path.abspath('.')
os.chdir(self.dir)
result = utils.extract_archive(self.arch_filepath, title)
os.chdir(curdir)
return result
def _validate_options(self):
logging.debug("_validate_options")
validation_result = super(ArchiveBase, self)._validate_options()
if not validation_result:
return False
if 'wrapper_archive' not in self.all_options:
logging.warning("Configuration lacks of optional `wrapper_archive'"
" option.\n")
wrapper_archive = self._get_wrapper_archive_name()
if wrapper_archive is None:
logging.error("Configuration lacks of optional "
"`wrapper_archive', cannot deduct the name by "
"configuration file name.\n")
validation_result = False
self.all_options['wrapper_archive'] = wrapper_archive
return validation_result
def _get_wrapper_archive_name(self):
"""
Return full path to the archive name using configuration file
basename and appending one of the expected archive extensions.
"""
basename = os.path.splitext(os.path.basename(self.conf_file))[0]
file_list = os.listdir(os.path.dirname(self.conf_file))
for fname in file_list:
for ext in ('.7z', '.lha', '.lzx', '.zip', '.rar', '.tar', '.tgz',
'.tar.gz', '.tar.bz2', '.tar.xz'):
if ((basename + ext).lower() == fname.lower() and
basename == os.path.splitext(fname)[0]):
return fname
return None

View File

@@ -8,12 +8,10 @@ used as a base for save state (it will append '_save.7z' to the archive file
name.
"""
import sys
from fs_uae_wrapper import base
class CD32(base.Base):
class Wrapper(base.ArchiveBase):
"""
Class for performing extracting archive, copying emulator files, and
cleaning it back again
@@ -30,11 +28,10 @@ class CD32(base.Base):
- run the emulation
- archive save state
"""
super(CD32, self).run()
super(Wrapper, self).run()
if not self._validate_options():
return False
self._set_assets_paths()
if not self._extract():
return False
@@ -42,31 +39,10 @@ class CD32(base.Base):
if not method():
return False
kick_opts = self._kickstart_option()
if kick_opts:
self.fsuae_options.update(kick_opts)
if not self._run_emulator():
return False
if self._run_emulator(self.fsuae_options.list()):
if self._get_saves_dir():
return self._save_save()
return True
def _validate_options(self):
validation_result = super(CD32, self)._validate_options()
if 'wrapper_archive' not in self.all_options:
sys.stderr.write("Configuration lacks of required "
"`wrapper_archive' option.\n")
validation_result = False
return validation_result
def run(config_file, fsuae_options, configuration):
"""Run fs-uae with provided config file and options"""
runner = CD32(config_file, fsuae_options, configuration)
try:
return runner.run()
finally:
runner.clean()

View File

@@ -1,48 +1,36 @@
"""
File archive classes
"""
import logging
import os
import subprocess
import sys
import re
import subprocess
def which(archivers):
"""
Check if there selected archiver is available in the system and place it
to the archiver attribute
"""
if not isinstance(archivers, list):
archivers = [archivers]
for fname in archivers:
for path in os.environ["PATH"].split(os.pathsep):
path = os.path.join(path.strip('"'), fname)
if os.path.isfile(path) and os.access(path, os.X_OK):
return fname
return None
from fs_uae_wrapper import path
class Archive(object):
"""Base class for archive support"""
ADD = ['a']
EXTRACT = ['x']
ADD = ('a',)
EXTRACT = ('x',)
ARCH = 'false'
def __init__(self):
self.archiver = which(self.ARCH)
self._compess = self.archiver
self._decompess = self.archiver
self.archiver = path.which(self.ARCH)
self._compress = self.archiver
self._decompress = self.archiver
def create(self, arch_name):
def create(self, arch_name, files=None):
"""
Create archive. Return True on success, False otherwise.
"""
result = subprocess.call([self._compess] + self.ADD + [arch_name, '.'])
files = files if files else ['.']
logging.debug("Calling `%s %s %s %s'.", self._compress,
" ".join(self.ADD), arch_name, " ".join(files))
result = subprocess.call([self._compress, *self.ADD, arch_name,
*files])
if result != 0:
sys.stderr.write("Unable to create archive `%s'\n" % arch_name)
logging.error("Unable to create archive `%s'.", arch_name)
return False
return True
@@ -51,33 +39,45 @@ class Archive(object):
Extract archive. Return True on success, False otherwise.
"""
if not os.path.exists(arch_name):
sys.stderr.write("Archive `%s' doesn't exists.\n" % arch_name)
logging.error("Archive `%s' doesn't exists.", arch_name)
return False
result = subprocess.call([self._decompess] + self.EXTRACT +
[arch_name])
logging.debug("Calling `%s %s %s'.", self._compress,
" ".join(self.ADD), arch_name)
result = subprocess.call([self._decompress, *self.EXTRACT, arch_name])
if result != 0:
sys.stderr.write("Unable to extract archive `%s'\n" % arch_name)
logging.error("Unable to extract archive `%s'.", arch_name)
return False
return True
class TarArchive(Archive):
ADD = ['cf']
EXTRACT = ['xf']
ADD = ('cf',)
EXTRACT = ('xf',)
ARCH = 'tar'
def create(self, arch_name, files=None):
files = files if files else sorted(os.listdir('.'))
logging.debug("Calling `%s %s %s %s'.", self._compress,
" ".join(self.ADD), arch_name, " ".join(files))
result = subprocess.call([self._compress, *self.ADD, arch_name,
*files])
if result != 0:
logging.error("Unable to create archive `%s'.", arch_name)
return False
return True
class TarGzipArchive(TarArchive):
ADD = ['zcf']
ADD = ('zcf',)
class TarBzip2Archive(TarArchive):
ADD = ['jcf']
ADD = ('jcf',)
class TarXzArchive(TarArchive):
ADD = ['Jcf']
ADD = ('Jcf',)
class LhaArchive(Archive):
@@ -85,13 +85,13 @@ class LhaArchive(Archive):
class ZipArchive(Archive):
ADD = ['a', '-tzip']
ARCH = ['7z', 'zip']
ADD = ('a', '-tzip')
ARCH = ('7z', 'zip')
def __init__(self):
super(ZipArchive, self).__init__()
if self.archiver == 'zip':
self._decompess = which('unzip')
self._decompress = path.which('unzip')
ZipArchive.ADD = ['-r']
ZipArchive.EXTRACT = []
@@ -101,33 +101,71 @@ class SevenZArchive(Archive):
class LzxArchive(Archive):
EXTRACT = ['-x']
EXTRACT = ('-x',)
ARCH = 'unlzx'
@classmethod
def create(self, arch_name):
sys.stderr.write('Cannot create LZX archive. Only extracting is'
'supported\n')
def create(self, arch_name, files=None):
logging.error('Cannot create LZX archive. Only extracting is'
'supported.')
return False
class RarArchive(Archive):
ARCH = ['rar', 'unrar']
ARCH = ('rar', 'unrar')
def create(self, arch_name):
def create(self, arch_name, files=None):
files = files if files else sorted(os.listdir('.'))
if self.archiver == 'unrar':
sys.stderr.write('Cannot create RAR archive. Only extracting is'
'supported by unrar.\n')
logging.error('Cannot create RAR archive. Only extracting is'
'supported by unrar.')
return False
result = subprocess.call([self._compess] + self.ADD + [arch_name] +
sorted(os.listdir('.')))
logging.debug("Calling `%s %s %s %s'.", self._compress,
" ".join(self.ADD), arch_name, " ".join(files))
result = subprocess.call([self._compress, *self.ADD, arch_name,
*files])
if result != 0:
sys.stderr.write("Unable to create archive `%s'\n" % arch_name)
logging.error("Unable to create archive `%s'.", arch_name)
return False
return True
class Archivers(object):
"""Archivers class"""
archivers = ({'arch': TarArchive, 'name': 'tar', 'ext': ['tar']},
{'arch': TarGzipArchive, 'name': 'tgz',
'ext': ['tar.gz', 'tgz']},
{'arch': TarBzip2Archive, 'name': 'tar.bz2',
'ext': ['tar.bz2']},
{'arch': TarXzArchive, 'name': 'tar.xz', 'ext': ['tar.xz']},
{'arch': RarArchive, 'name': 'rar', 'ext': ['rar']},
{'arch': SevenZArchive, 'name': '7z', 'ext': ['7z']},
{'arch': ZipArchive, 'name': 'zip', 'ext': ['zip']},
{'arch': LhaArchive, 'name': 'lha', 'ext': ['lha', 'lzh']},
{'arch': LzxArchive, 'name': 'lzx', 'ext': ['lzx']})
@classmethod
def get(cls, extension):
"""
Get the archive class or None
"""
for arch in cls.archivers:
if extension in arch['ext']:
return arch['arch']
return None
@classmethod
def get_extension_by_name(cls, name):
"""
Get the first defined extension for the archive format
"""
for arch in cls.archivers:
if name == arch['name']:
return '.' + arch['ext'][0]
return None
def get_archiver(arch_name):
"""Return right class for provided archive file name"""
@@ -138,26 +176,18 @@ def get_archiver(arch_name):
if result:
ext = result.groups()[0]
archivers = {'.tar': TarArchive,
'.tgz': TarGzipArchive,
'.tar.gz': TarGzipArchive,
'.tar.bz2': TarBzip2Archive,
'.tar.xz': TarXzArchive,
'.rar': RarArchive,
'.7z': SevenZArchive,
'.zip': ZipArchive,
'.lha': LhaArchive,
'.lzx': LzxArchive}
if ext:
ext = ext[1:]
archiver = archivers.get(ext)
archiver = Archivers.get(ext)
if not archiver:
sys.stderr.write("Unable find archive type for `%s'\n" % arch_name)
logging.error("Unable find archive type for `%s'.", arch_name)
return None
archobj = archiver()
if archobj.archiver is None:
sys.stderr.write("Unable find executable for operating on files"
" `*%s'\n" % ext)
logging.error("Unable find executable for operating on files `*%s'.",
ext)
return None
return archobj

View File

@@ -3,19 +3,15 @@ Display message in separate process
"""
import multiprocessing as mp
import sys
try:
import tkinter as tk
from tkinter import ttk
except ImportError:
import Tkinter as tk
import ttk
import tkinter
import tkinter.ttk
class MessageGui(tk.Tk):
class MessageGui(tkinter.Tk):
"""Simple gui for displaying a message"""
def __init__(self, parent=None, msg=""):
tk.Tk.__init__(self, parent)
tkinter.Tk.__init__(self, parent)
self.grid()
self.resizable(False, False)
@@ -24,12 +20,13 @@ class MessageGui(tk.Tk):
# Display window without decorations
self.wm_attributes('-type', 'splash')
self.frame = ttk.Frame(self, padding=5, borderwidth=0)
self.frame = tkinter.ttk.Frame(self, padding=5, borderwidth=0)
self.frame.grid()
ttk.Label(self.frame, text=msg, relief="ridge", padding=10).grid()
tkinter.ttk.Label(self.frame, text=msg, relief="ridge",
padding=10).grid()
if 'linux' in sys.platform:
style = ttk.Style()
style = tkinter.ttk.Style()
style.theme_use('clam')
def __call__(self):

22
fs_uae_wrapper/path.py Normal file
View File

@@ -0,0 +1,22 @@
"""
Misc utilities
"""
import os
def which(executables):
"""
Check if there selected archiver is available in the system and place it
to the archiver attribute
"""
if not isinstance(executables, list):
executables = [executables]
for fname in executables:
for path in os.environ["PATH"].split(os.pathsep):
path = os.path.join(path.strip('"'), fname)
if os.path.isfile(path) and os.access(path, os.X_OK):
return fname
return None

View File

@@ -1,27 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Simple class for executing fs-uae with specified parameters. This is a
failsafe class for running fs-uae.
"""
import subprocess
import sys
from fs_uae_wrapper import base, utils
def run_plain(conf_file, fs_uae_options):
"""
Run the emulation.
conf_file is a path to the configuration,
fs_uae_options is an dict-like object which contains commandline options to
be passed to fs-uae
"""
try:
subprocess.call(['fs-uae'] + [conf_file] + fs_uae_options.list())
except subprocess.CalledProcessError:
sys.stderr.write('Warning: fs-uae returned non 0 exit code\n')
return True
class Wrapper(base.Base):
"""Simple class for running fs-uae"""
def run(self):
"""
Main function which run FS-UAE
"""
self._run_emulator()
def run(config_file, fs_uae_options, _):
"""Run fs-uae with provided config file and options"""
return run_plain(config_file, fs_uae_options)
def _run_emulator(self):
"""execute fs-uae"""
utils.run_command(['fs-uae', self.conf_file,
*self.fsuae_options.list()])
def clean(self):
"""Do the cleanup. Here - just do nothing"""
return

View File

@@ -0,0 +1,48 @@
"""
Wrapper module with preserved save state in archive file.
"""
from fs_uae_wrapper import base
class Wrapper(base.Base):
"""
Preserve save state.
"""
def __init__(self, conf_file, fsuae_options, configuration):
"""
Params:
conf_file: a relative path to provided configuration file
fsuae_options: is an CmdOption object created out of command line
parameters
configuration: is config dictionary created out of config file
"""
super(Wrapper, self).__init__(conf_file, fsuae_options, configuration)
self.arch_filepath = None
self.all_options['wrapper_save_state'] = '1'
def run(self):
"""
Main function which accepts configuration file for FS-UAE
It will do as follows:
- set needed full path for asset files
- copy configuration
- optionally extract save state archive
- run the emulation
- optionally make archive save state
"""
if not super(Wrapper, self).run():
return False
self._load_save()
if not self._copy_conf():
return False
if not self._run_emulator():
return False
if self._get_saves_dir():
if not self._save_save():
return False
return True

View File

@@ -1,17 +1,14 @@
"""
Misc utilities
"""
from distutils import spawn
import configparser
import logging
import os
import pathlib
import shutil
import subprocess
import sys
try:
import configparser
except ImportError:
import ConfigParser as configparser
from fs_uae_wrapper import message
from fs_uae_wrapper import file_archive
from fs_uae_wrapper import file_archive, message
class CmdOption(dict):
@@ -22,8 +19,8 @@ class CmdOption(dict):
def add(self, option):
"""parse and add option to the dictionary"""
if not option.startswith('--'):
raise AttributeError("Cannot add option `%s' to the dictionary" %
option)
raise AttributeError(f"Cannot add option {option} to the "
f"dictionary")
if '=' in option:
key, val = option.split('=', 1)
key = key[2:].strip()
@@ -38,15 +35,15 @@ class CmdOption(dict):
ret_list = []
for key, val in self.items():
if val != '1':
ret_list.append('--%(k)s=%(v)s' % {'k': key, 'v': val})
ret_list.append(f'--{key}={val}')
else:
ret_list.append('--%(k)s' % {'k': key})
ret_list.append(f'--{key}')
return ret_list
def get_config_options(conf):
"""Read config file and return options as a dict"""
parser = configparser.SafeConfigParser()
parser = configparser.ConfigParser()
try:
parser.read(conf)
except configparser.ParsingError:
@@ -57,7 +54,7 @@ def get_config_options(conf):
for key, val in parser.items(section)}
def operate_archive(arch_name, operation, text):
def operate_archive(arch_name, operation, text, params):
"""
Create archive from contents of current directory
"""
@@ -77,31 +74,31 @@ def operate_archive(arch_name, operation, text):
res = archiver.extract(arch_name)
if operation == 'create':
res = archiver.create(arch_name)
res = archiver.create(arch_name, params)
msg.close()
return res
def create_archive(arch_name, title=''):
def create_archive(arch_name, title='', params=None):
"""
Create archive from contents of current directory
"""
msg = ''
if title:
msg = "Creating archive for `%s'. Please be patient" % title
return operate_archive(arch_name, 'create', msg)
msg = f"Creating archive for `{title}'. Please be patient"
return operate_archive(arch_name, 'create', msg, params)
def extract_archive(arch_name, title=''):
def extract_archive(arch_name, title='', params=None):
"""
Extract provided archive to current directory
"""
msg = ''
if title:
msg = "Extracting files for `%s'. Please be patient" % title
return operate_archive(arch_name, 'extract', msg)
msg = f"Extracting files for `{title}'. Please be patient"
return operate_archive(arch_name, 'extract', msg, params)
def run_command(cmd):
@@ -115,10 +112,10 @@ def run_command(cmd):
if not isinstance(cmd, list):
cmd = cmd.split()
logging.debug("Executing `%s'.", " ".join(cmd))
code = subprocess.call(cmd)
if code != 0:
sys.stderr.write('Command `{0}` returned non 0 exit '
'code\n'.format(cmd[0]))
logging.error('Command `%s` returned non 0 exit code.', cmd[0])
return False
return True
@@ -144,17 +141,20 @@ def interpolate_variables(string, config_path, base=None):
- $BASE
"""
_string = string
if '$CONFIG' in string:
string = string.replace('$CONFIG',
os.path.dirname(os.path.abspath(config_path)))
conf_path = pathlib.Path(config_path).resolve().parent
string = str(pathlib.Path(string.replace('$CONFIG', str(conf_path)))
.resolve())
if '$HOME' in string:
string = string.replace('$HOME', os.path.expandvars('$HOME'))
if '$EXE' in string:
string = string.replace('$EXE', spawn.find_executable('fs-uae'))
string = string.replace('$EXE', shutil.which('fs-uae'))
if '$APP' in string:
string = string.replace('$APP', spawn.find_executable('fs-uae'))
string = string.replace('$APP', shutil.which('fs-uae'))
if '$DOCUMENTS' in string:
xdg_docs = os.getenv('XDG_DOCUMENTS_DIR',
@@ -165,7 +165,9 @@ def interpolate_variables(string, config_path, base=None):
if '$BASE' in string:
string = string.replace('$BASE', base)
return string
if os.path.exists(string):
return string
return _string
def get_config(conf_file):
@@ -217,3 +219,8 @@ def get_config(conf_file):
config['_base_dir'] = conf_dir
return config
def get_arch_ext(archiver_name):
"""Return extension for the archiver"""
return file_archive.Archivers.get_extension_by_name(archiver_name)

136
fs_uae_wrapper/whdload.py Normal file
View File

@@ -0,0 +1,136 @@
"""
Run fs-uae with WHDLoad games
It will use compressed base image and compressed directories.
"""
import logging
import os
from fs_uae_wrapper import base, utils
class Wrapper(base.ArchiveBase):
"""
Class for performing extracting archive, copying emulator files, and
cleaning it back again
"""
def __init__(self, conf_file, fsuae_options, configuration):
super(Wrapper, self).__init__(conf_file, fsuae_options, configuration)
self.archive_type = None
def run(self):
"""
Main function which accepts configuration file for FS-UAE
It will do as follows:
- extract base image and archive file
- copy configuration
- run the emulation
"""
logging.debug("run")
if not super().run():
return False
if not self._extract():
return False
if not self._copy_conf():
return False
return self._run_emulator()
def _validate_options(self):
"""
Do the validation for the options, additionally check if there is
mandatory WHDLoad base OS images set.
"""
if not super()._validate_options():
return False
if not self.all_options.get('wrapper_whdload_base'):
logging.error("wrapper_whdload_base is not set in configuration, "
"exiting.")
return False
return True
def _extract(self):
"""Extract base image and then WHDLoad archive"""
base_image = self.fsuae_options['wrapper_whdload_base']
if not os.path.exists(base_image):
logging.error("Base image `%s` does't exists in provided "
"location.", base_image)
return False
curdir = os.path.abspath('.')
os.chdir(self.dir)
result = utils.extract_archive(base_image)
os.chdir(curdir)
if not result:
return False
if not super()._extract():
return False
return self._find_slave()
def _find_slave(self):
"""Find Slave file and create apropriate entry in S:whdload-startup"""
curdir = os.path.abspath('.')
os.chdir(self.dir)
# find slave name
slave_fname = None
slave_path = None
case_insensitvie_map = {}
# build case insensitive map of paths and find the slave file
for root, dirnames, fnames in os.walk('.'):
for dirname in dirnames:
full_path = os.path.normpath(os.path.join(root, dirname))
case_insensitvie_map[full_path.lower()] = full_path
for fname in fnames:
full_path = os.path.normpath(os.path.join(root, fname))
case_insensitvie_map[full_path.lower()] = full_path
if not slave_fname and fname.lower().endswith('.slave'):
slave_path, slave_fname = os.path.normpath(root), fname
if slave_fname is None:
logging.error("Cannot find .slave file in archive.")
return False
# find corresponfing info (an icon) fname
icon_fname = None
for fname in os.listdir(slave_path):
if (fname.lower().endswith('.info') and
os.path.splitext(slave_fname)[0].lower() ==
os.path.splitext(fname)[0].lower()):
icon_fname = fname
break
if icon_fname is None:
logging.error("Cannot find .info file corresponding to %s in "
"archive.", slave_fname)
return False
# find proper way to handle slave
# 1. check if there are user provided params
contents = f"cd {slave_path}\n"
if self.all_options.get('wrapper_whdload_options'):
contents = (f"{contents}"
f"C:whdload "
f"{self.all_options['wrapper_whdload_options']} "
f"Slave={slave_fname}\n")
else:
# no params, find if kgiconload is available
if case_insensitvie_map.get('c/kgiconload'):
contents = f"{contents}C:kgiconload {icon_fname}\n"
else:
# if not, just add common defaults
contents = (f"{contents}C:whdload Preload "
f"Slave={slave_fname}\n")
fname = os.path.join(case_insensitvie_map.get('s'), 'whdload-startup')
with open(fname, "w") as fobj:
fobj.write(contents)
os.chdir(curdir)
return True

View File

@@ -1,14 +1,33 @@
#!/usr/bin/env python
"""
Wrapper for FS-UAE to perform some actions before and or after running the
emulator, if appropriate option is enabled.
"""
import importlib
import logging
import os
import sys
from fs_uae_wrapper import utils
from fs_uae_wrapper import WRAPPER_KEY
from fs_uae_wrapper import WRAPPER_KEY, utils
def setup_logger(options):
"""Setup logger format and level"""
level = logging.WARNING
if options['wrapper_quiet']:
level = logging.ERROR
if options['wrapper_quiet'] > 1:
level = logging.CRITICAL
if options['wrapper_verbose']:
level = logging.INFO
if options['wrapper_verbose'] > 1:
level = logging.DEBUG
logging.basicConfig(level=level,
format="%(asctime)s %(levelname)s\t%(filename)s:"
"%(lineno)d:\t\t%(message)s")
def parse_args():
@@ -18,7 +37,16 @@ def parse_args():
"""
fs_conf = None
options = utils.CmdOption()
options['wrapper_verbose'] = 0
options['wrapper_quiet'] = 0
for parameter in sys.argv[1:]:
if parameter in ['-v', '-q']:
if parameter == '-v':
options['wrapper_verbose'] += 1
if parameter == '-q':
options['wrapper_quiet'] += 1
continue
try:
options.add(parameter)
except AttributeError:
@@ -33,7 +61,7 @@ def parse_args():
def usage():
"""Print help"""
sys.stdout.write("Usage: %s [conf-file] [fs-uae-option...]\n\n"
sys.stdout.write("Usage: %s [conf-file] [-v] [-q] [fs-uae-option...]\n\n"
% sys.argv[0])
sys.stdout.write("Config file is not required, if `Config.fs-uae' "
"exists in the current\ndirectory, although it might "
@@ -46,24 +74,27 @@ def usage():
def run():
"""run wrapper module"""
config_file, cmd_options = parse_args()
config_file, fsuae_options = parse_args()
setup_logger(fsuae_options)
del fsuae_options['wrapper_verbose']
del fsuae_options['wrapper_quiet']
if 'help' in cmd_options:
if 'help' in fsuae_options:
usage()
sys.exit(0)
if not config_file:
sys.stderr.write('Error: Configuration file not found\nSee --help'
' for usage\n')
logging.error('Error: Configuration file not found. See --help'
' for usage')
sys.exit(1)
configuration = utils.get_config_options(config_file)
if configuration is None:
sys.stderr.write('Error: Configuration file have syntax issues\n')
logging.error('Error: Configuration file have syntax issues')
sys.exit(2)
wrapper_module = cmd_options.get(WRAPPER_KEY)
wrapper_module = fsuae_options.get(WRAPPER_KEY)
if not wrapper_module:
wrapper_module = configuration.get(WRAPPER_KEY)
@@ -74,13 +105,16 @@ def run():
wrapper = importlib.import_module('fs_uae_wrapper.' +
wrapper_module)
except ImportError:
sys.stderr.write("Error: provided wrapper module: `%s' doesn't "
"exists.\n" % wrapper_module)
logging.error("Error: provided wrapper module: `%s' doesn't "
"exists.", wrapper_module)
sys.exit(3)
if not wrapper.run(config_file, cmd_options, configuration):
runner = wrapper.Wrapper(config_file, fsuae_options, configuration)
try:
exit_code = runner.run()
finally:
runner.clean()
if not exit_code:
sys.exit(4)
if __name__ == "__main__":
run()

50
pyproject.toml Normal file
View File

@@ -0,0 +1,50 @@
[build-system]
requires = ["setuptools >= 77.0", "wheel", "setuptools-git-versioning"]
build-backend = "setuptools.build_meta"
[project]
name = "fs-uae-wrapper"
authors = [{name = "Roman Dobosz", email = "gryf73@gmail.com"}]
license = "BSD-3-Clause"
description = "Automate archives support and state saves for fs-uae"
readme = "README.rst"
requires-python = ">=3.8"
keywords = ["uae", "fs-uae", "amiga", "emulator", "wrapper"]
dynamic = ["version"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: System :: Emulators",
"Topic :: Games/Entertainment"
]
[project.urls]
Homepage = "https://github.com/gryf/fs-uae-wrapper"
[project.scripts]
fs-uae-wrapper = "fs_uae_wrapper.wrapper:run"
[tool.setuptools]
packages = ["fs_uae_wrapper"]
[tool.distutils.bdist_wheel]
universal = true
[tool.setuptools-git-versioning]
enabled = true
[tool.ruff.lint]
select = [
"F", # pyflakes
"E", # pycodestyle
"I", # isort
"RUF", # ruff-specific rules
]

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Run CD32 games using fsuae
"""
from fs_uae_wrapper import wrapper
def main():
"""run wrapper"""
wrapper.run()
if __name__ == "__main__":
main()

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python
"""
Setup for the fs-uae-wrapper
"""
from distutils.core import setup
setup(name='fs-uae-wrapper',
packages=['fs_uae_wrapper'],
version='0.3',
description='Automate archives and state for fs-uae',
author='Roman Dobosz',
author_email='gryf73@gmail.com',
url='https://github.com/gryf/fs-uea-wrapper',
download_url='https://github.com/gryf/pygtktalog.git',
keywords=['uae', 'fs-uae', 'amiga', 'emulator', 'wrapper'],
scripts=['script/fs-uae-wrapper'],
classifiers=['Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Topic :: System :: Emulators',
'Topic :: Games/Entertainment'],
long_description=open('README.rst').read(),
options={'test': {'verbose': False,
'coverage': False}})

View File

@@ -1,5 +0,0 @@
pytest
pytest-cov
pytest-pep8
coverage
flake8

View File

@@ -1,15 +1,9 @@
import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase
from unittest import TestCase, mock
try:
from unittest import mock
except ImportError:
import mock
from fs_uae_wrapper import archive
from fs_uae_wrapper import utils
from fs_uae_wrapper import archive, utils
class TestArchive(TestCase):
@@ -26,75 +20,89 @@ class TestArchive(TestCase):
except OSError:
pass
def test_validate_options(self):
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
@mock.patch('fs_uae_wrapper.path.which')
def test_validate_options(self, which, get_wrapper_arch_name):
which.return_value = 'unrar'
arch = archive.Archive('Config.fs-uae', utils.CmdOption(), {})
arch = archive.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(arch._validate_options())
arch.all_options['wrapper'] = 'archive'
get_wrapper_arch_name.return_value = None
arch.all_options = {'wrapper': 'archive'}
self.assertFalse(arch._validate_options())
arch.all_options['wrapper_archive'] = 'fake.tgz'
get_wrapper_arch_name.return_value = 'fake_arch_filename'
arch.all_options['wrapper_archive'] = 'rar'
self.assertTrue(arch._validate_options())
@mock.patch('tempfile.mkdtemp')
@mock.patch('fs_uae_wrapper.archive.Archive._make_archive')
@mock.patch('fs_uae_wrapper.base.Base._run_emulator')
@mock.patch('fs_uae_wrapper.base.Base._kickstart_option')
@mock.patch('fs_uae_wrapper.base.Base._copy_conf')
@mock.patch('fs_uae_wrapper.base.Base._load_save')
@mock.patch('fs_uae_wrapper.base.Base._extract')
def test_run(self, extr, load, copy, kick, run, march, mkdtemp):
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('fs_uae_wrapper.archive.Wrapper._make_archive')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._save_save')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._load_save')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
def test_run(self, extract, load_save, copy_conf, run_emulator,
get_save_dir, save_state, get_wrapper_arch_name, make_arch,
which, mkdtemp):
extr.return_value = False
load.return_value = False
copy.return_value = False
kick.return_value = False
run.return_value = False
march.return_value = False
extract.return_value = False
load_save.return_value = False
copy_conf.return_value = False
run_emulator.return_value = False
get_save_dir.return_value = False
save_state.return_value = False
get_wrapper_arch_name.return_value = "fake_arch_filename"
make_arch.return_value = False
which.return_value = 'rar'
arch = archive.Archive('Config.fs-uae', utils.CmdOption(), {})
arch = archive.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(arch.run())
arch.all_options = {'wrapper': 'archive',
'wrapper_archive': 'fake.tgz'}
'wrapper_archive': 'fake.tgz',
'wrapper_archiver': 'rar'}
self.assertFalse(arch.run())
extr.return_value = True
extract.return_value = True
self.assertFalse(arch.run())
load.return_value = True
load_save.return_value = True
self.assertFalse(arch.run())
copy.return_value = True
copy_conf.return_value = True
self.assertFalse(arch.run())
kick.return_value = {'foo': 'bar'}
self.assertFalse(arch.run())
self.assertDictEqual(arch.fsuae_options, {'foo': 'bar'})
run.return_value = True
run_emulator.return_value = True
self.assertFalse(arch.run())
march.return_value = True
get_save_dir.return_value = True
self.assertFalse(arch.run())
save_state.return_value = True
self.assertFalse(arch.run())
make_arch.return_value = True
self.assertTrue(arch.run())
@mock.patch('os.rename')
@mock.patch('os.unlink')
@mock.patch('shutil.rmtree')
@mock.patch('fs_uae_wrapper.utils.create_archive')
@mock.patch('fs_uae_wrapper.base.Base._get_title')
@mock.patch('fs_uae_wrapper.base.Base._save_save')
@mock.patch('fs_uae_wrapper.base.Base._get_saves_dir')
def test_make_archive(self, sdir, save, title, carch, rmt, unlink, rename):
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_title')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
def test_make_archive(self, sdir, title, carch, rmt, unlink, rename):
sdir.return_value = None
save.return_value = False
title.return_value = ''
carch.return_value = False
arch = archive.Archive('Config.fs-uae', utils.CmdOption(), {})
arch = archive.Wrapper('Config.fs-uae', utils.CmdOption(), {})
arch.dir = self.dirname
arch.arch_filepath = os.path.join(self.dirname, 'foo.tgz')
arch.all_options = {}
@@ -107,7 +115,4 @@ class TestArchive(TestCase):
self.assertTrue(arch._make_archive())
sdir.return_value = '/some/path'
self.assertFalse(arch._make_archive())
save.return_value = True
self.assertTrue(arch._make_archive())

View File

@@ -1,16 +1,10 @@
import os
import sys
import shutil
from tempfile import mkstemp, mkdtemp
from unittest import TestCase
import sys
from tempfile import mkdtemp, mkstemp
from unittest import TestCase, mock
try:
from unittest import mock
except ImportError:
import mock
from fs_uae_wrapper import base
from fs_uae_wrapper import utils
from fs_uae_wrapper import base, utils
class TestBase(TestCase):
@@ -47,41 +41,94 @@ class TestBase(TestCase):
bobj.clean()
self.assertFalse(os.path.exists(self.dirname))
@mock.patch('os.path.exists')
@mock.patch('fs_uae_wrapper.utils.get_config')
def test_kickstart_option(self, get_config):
def test_normalize_options(self, get_config, os_exists):
os_exists.return_value = True
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
get_config.return_value = {'foo': 'bar'}
self.assertDictEqual(bobj._kickstart_option(), {})
get_config.return_value = {'kickstarts_dir': '/some/path'}
self.assertDictEqual(bobj._kickstart_option(),
{'kickstarts_dir': '/some/path'})
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {})
os.chdir(self.dirname)
get_config.return_value = {'kickstarts_dir': '../some/path'}
get_config.return_value = {'fmv_rom': 'bar'}
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options,
{'fmv_rom': os.path.join(self.dirname, 'bar')})
get_config.return_value = {'floppies_dir': '../some/path'}
bobj.fsuae_options = utils.CmdOption()
result = os.path.abspath(os.path.join(self.dirname, '../some/path'))
self.assertDictEqual(bobj._kickstart_option(),
{'kickstarts_dir': result})
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {'floppies_dir': result})
bobj.conf_file = os.path.join(self.dirname, 'Config.fs-uae')
get_config.return_value = {'kickstarts_dir': '$CONFIG/../path'}
get_config.return_value = {'cdroms_dir': '$CONFIG/../path'}
bobj.fsuae_options = utils.CmdOption()
result = os.path.abspath(os.path.join(self.dirname, '../path'))
self.assertDictEqual(bobj._kickstart_option(),
{'kickstarts_dir': result})
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {'cdroms_dir': result})
get_config.return_value = {'cdroms_dir': '$HOME/path'}
bobj.fsuae_options = utils.CmdOption()
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {})
get_config.return_value = {'cdroms_dir': '$WRAPPER/path'}
bobj.fsuae_options = utils.CmdOption()
bobj.dir = self.dirname
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options,
{'cdroms_dir': os.path.join(bobj.dir, 'path')})
get_config.return_value = {'cdroms_dir': '~/path'}
bobj.fsuae_options = utils.CmdOption()
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {})
get_config.return_value = {'random_item': 10}
bobj.fsuae_options = utils.CmdOption()
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {})
@mock.patch('os.path.exists')
@mock.patch('fs_uae_wrapper.utils.get_config')
def test_normalize_options_path_not_exists(self, get_config, os_exists):
os_exists.return_value = False
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
get_config.return_value = {'kickstarts_dir': '/some/path'}
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {})
os.chdir(self.dirname)
get_config.return_value = {'fmv_rom': 'bar'}
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options, {'fmv_rom': 'bar'})
get_config.return_value = {'floppies_dir': '../some/path'}
bobj.fsuae_options = utils.CmdOption()
bobj._normalize_options()
self.assertDictEqual(bobj.fsuae_options,
{'floppies_dir': '../some/path'})
def test_set_assets_paths(self):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
os.chdir(self.dirname)
bobj.conf_file = 'Config.fs-uae'
bobj.all_options = {'wrapper_archive': 'foo.7z'}
bobj.all_options = {'wrapper_archive': 'foo.7z',
'wrapper_archiver': '7z'}
bobj._set_assets_paths()
full_path = os.path.join(self.dirname, 'Config_save.7z')
self.assertEqual(bobj.save_filename, full_path)
bobj.all_options = {'wrapper_archive': '/home/user/foo.7z'}
bobj.all_options = {'wrapper_archive': '/home/user/foo.7z',
'wrapper_archiver': '7z'}
bobj._set_assets_paths()
full_path = os.path.join(self.dirname, 'Config_save.7z')
@@ -97,10 +144,227 @@ class TestBase(TestCase):
self.assertTrue(os.path.exists(os.path.join(self.dirname,
'Config.fs-uae')))
@mock.patch('fs_uae_wrapper.utils.run_command')
def test_run_emulator(self, run):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
self.assertTrue(bobj._run_emulator())
run.assert_called_once_with(['fs-uae'])
# Errors from emulator are not fatal to wrappers
run.reset_mock()
run.return_value = False
self.assertTrue(bobj._run_emulator())
run.assert_called_once_with(['fs-uae'])
# pass the options
bobj.fsuae_options = utils.CmdOption({'foo': '1'})
run.reset_mock()
run.return_value = False
self.assertTrue(bobj._run_emulator())
run.assert_called_once_with(['fs-uae', '--foo'])
@mock.patch('fs_uae_wrapper.base.Base._get_saves_dir')
@mock.patch('fs_uae_wrapper.utils.create_archive')
def test_save_save(self, carch, saves_dir):
os.chdir(self.confdir)
bobj = base.Base('myconf.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
bobj.save_filename = os.path.join(self.confdir, 'myconf_save.7z')
saves_dir.return_value = None
carch.return_value = True
self.assertTrue(bobj._save_save(),
'there is assumption, that wrapper_save_state is'
' false by default. Here it was true.')
bobj.all_options['wrapper_save_state'] = '1'
self.assertTrue(bobj._save_save(),
'unexpected save_state directory found')
saves_dir.return_value = bobj.save_filename
with open(bobj.save_filename, 'w') as fobj:
fobj.write('asd')
os.mkdir(os.path.join(self.dirname, 'fs-uae-save'))
self.assertTrue(bobj._save_save())
carch.return_value = False
self.assertFalse(bobj._save_save())
@mock.patch('fs_uae_wrapper.utils.extract_archive')
def test_load_save(self, earch):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
bobj.save_filename = "foobar_save.7z"
earch.return_value = 0
# By default there would be no save state persistence
self.assertTrue(bobj._load_save())
# set wrapper_save_state option, so we can proceed with test
bobj.all_options['wrapper_save_state'] = '1'
# fail to load save is not fatal
self.assertTrue(bobj._load_save())
os.chdir(self.confdir)
with open(bobj.save_filename, 'w') as fobj:
fobj.write('asd')
self.assertTrue(bobj._load_save())
earch.assert_called_once_with(bobj.save_filename)
# failure in searching for archiver are also non fatal
earch.reset_mock()
earch.return_value = 1
self.assertTrue(bobj._save_save())
def test_get_saves_dir(self):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '/some/path'
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '$WRAPPER/../saves'
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '/foo/$WRAPPER/saves'
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '$WRAPPER/saves'
self.assertIsNone(bobj._get_saves_dir())
path = os.path.join(self.dirname, 'saves')
with open(path, 'w') as fobj:
fobj.write('\n')
self.assertIsNone(bobj._get_saves_dir())
os.unlink(path)
os.mkdir(path)
self.assertEqual(bobj._get_saves_dir(), 'saves')
bobj.all_options['save_states_dir'] = '$WRAPPER/saves/'
self.assertEqual(bobj._get_saves_dir(), 'saves')
@mock.patch('fs_uae_wrapper.path.which')
def test_validate_options(self, which):
which.return_value = None
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {}
self.assertFalse(bobj._validate_options())
bobj.all_options = {'wrapper': 'dummy'}
self.assertTrue(bobj._validate_options())
bobj.all_options = {'wrapper': 'dummy',
'wrapper_save_state': '0'}
self.assertTrue(bobj._validate_options())
bobj.all_options = {'wrapper': 'dummy',
'wrapper_archiver': 'rar'}
self.assertTrue(bobj._validate_options())
bobj.all_options = {'wrapper': 'dummy',
'wrapper_save_state': '1'}
self.assertFalse(bobj._validate_options())
which.return_value = '7z'
bobj.all_options = {'wrapper': 'dummy',
'wrapper_save_state': '1'}
self.assertTrue(bobj._validate_options())
bobj.all_options = {'wrapper': 'dummy',
'wrapper_save_state': '1',
'wrapper_archiver': '7z'}
self.assertTrue(bobj._validate_options())
which.return_value = None
bobj.all_options = {'wrapper': 'dummy',
'wrapper_save_state': '1',
'wrapper_archiver': '7z'}
self.assertFalse(bobj._validate_options())
@mock.patch('fs_uae_wrapper.path.which')
def test_run_clean(self, which):
which.return_value = 'rar'
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {}
self.assertFalse(bobj.run())
bobj.all_options = {'wrapper': 'dummy',
'wrapper_archiver': 'rar',
'wrapper_archive': 'foo.7z'}
try:
self.assertTrue(bobj.run())
self.assertTrue(os.path.exists(bobj.dir))
finally:
bobj.clean()
class TestArchiveBase(TestCase):
def setUp(self):
fd, self.fname = mkstemp()
self.dirname = mkdtemp()
self.confdir = mkdtemp()
os.close(fd)
self._argv = sys.argv[:]
sys.argv = ['fs-uae-wrapper']
self.curdir = os.path.abspath(os.curdir)
def tearDown(self):
os.chdir(self.curdir)
try:
shutil.rmtree(self.dirname)
except OSError:
pass
try:
shutil.rmtree(self.confdir)
except OSError:
pass
os.unlink(self.fname)
sys.argv = self._argv[:]
def test_set_assets_paths(self):
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
os.chdir(self.dirname)
bobj.conf_file = 'Config.fs-uae'
bobj.all_options = {'wrapper_archive': 'foo.7z',
'wrapper_archiver': '7z'}
bobj._set_assets_paths()
full_path = os.path.join(self.dirname, 'Config_save.7z')
self.assertEqual(bobj.save_filename, full_path)
bobj.all_options = {'wrapper_archive': '/home/user/foo.7z',
'wrapper_archiver': '7z'}
bobj._set_assets_paths()
full_path = os.path.join(self.dirname, 'Config_save.7z')
self.assertEqual(bobj.save_filename, full_path)
@mock.patch('fs_uae_wrapper.utils.extract_archive')
def test_extract(self, utils_extract):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
bobj.arch_filepath = self.fname
bobj.dir = self.dirname
@@ -125,117 +389,42 @@ class TestBase(TestCase):
self.assertFalse(bobj._extract())
utils_extract.assert_called_once_with(self.fname, '')
@mock.patch('fs_uae_wrapper.utils.run_command')
def test_run_emulator(self, run):
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
def test_validate_options(self, get_wrapper_arch_name):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
self.assertTrue(bobj._run_emulator([]))
run.assert_called_once_with(['fs-uae'])
# Errors from emulator are not fatal to wrappers
run.reset_mock()
run.return_value = False
self.assertTrue(bobj._run_emulator([]))
run.assert_called_once_with(['fs-uae'])
@mock.patch('fs_uae_wrapper.base.Base._get_saves_dir')
@mock.patch('fs_uae_wrapper.utils.run_command')
def test_save_save(self, run, saves_dir):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
bobj.save_filename = 'foobar_save.7z'
saves_dir.bobj.save_filenamereturn_value = None
run.return_value = True
self.assertTrue(bobj._save_save())
saves_dir.return_value = bobj.save_filename
os.chdir(self.confdir)
with open(bobj.save_filename, 'w') as fobj:
fobj.write('asd')
self.assertTrue(bobj._save_save())
os.mkdir(os.path.join(self.dirname, 'fs-uae-save'))
self.assertTrue(bobj._save_save())
run.return_value = False
self.assertFalse(bobj._save_save())
@mock.patch('fs_uae_wrapper.utils.run_command')
def test_load_save(self, run):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
bobj.save_filename = "foobar_save.7z"
run.return_value = 0
# fail to load save is not fatal
self.assertTrue(bobj._load_save())
os.chdir(self.confdir)
with open(bobj.save_filename, 'w') as fobj:
fobj.write('asd')
self.assertTrue(bobj._load_save())
run.assert_called_once_with(['7z', 'x', bobj.save_filename])
# failure in searching for archiver are also non fatal
run.reset_mock()
run.return_value = 1
self.assertTrue(bobj._save_save())
def test_get_saves_dir(self):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.dir = self.dirname
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '/some/path'
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '$CONFIG/../saves'
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '/foo/$CONFIG/saves'
self.assertIsNone(bobj._get_saves_dir())
bobj.all_options['save_states_dir'] = '$CONFIG/saves'
self.assertIsNone(bobj._get_saves_dir())
path = os.path.join(self.dirname, 'saves')
with open(path, 'w') as fobj:
fobj.write('\n')
self.assertIsNone(bobj._get_saves_dir())
os.unlink(path)
os.mkdir(path)
self.assertEqual(bobj._get_saves_dir(), path)
def test_validate_options(self):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {}
self.assertFalse(bobj._validate_options())
get_wrapper_arch_name.return_value = None
bobj.all_options = {'wrapper': 'dummy'}
self.assertFalse(bobj._validate_options())
bobj.all_options = {'wrapper': 'dummy',
'wrapper_archive': 'myarchive.7z'}
self.assertTrue(bobj._validate_options())
def test_run_clean(self):
bobj = base.Base('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {}
self.assertFalse(bobj.run())
@mock.patch('os.listdir')
def test_get_wrapper_archive_name(self, os_listdir):
os_listdir.return_value = 'no archive among other files'.split()
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {'wrapper': 'dummy'}
try:
self.assertTrue(bobj.run())
self.assertTrue(os.path.exists(bobj.dir))
finally:
bobj.clean()
self.assertIsNone(bobj._get_wrapper_archive_name())
os_listdir.return_value = 'no config.rar among other files'.split()
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {'wrapper': 'dummy'}
self.assertIsNone(bobj._get_wrapper_archive_name())
os_listdir.return_value = 'file Config.TAR among other files'.split()
bobj = base.ArchiveBase('Config.fs-uae', utils.CmdOption(), {})
bobj.all_options = {'wrapper': 'dummy'}
self.assertEqual(bobj._get_wrapper_archive_name(), 'Config.TAR')
os_listdir.return_value = 'Config.lha FooBar_1.24b_20202.7z'.split()
bobj = base.ArchiveBase('FooBar_1.24b_20202.fs-uae',
utils.CmdOption(), {})
bobj.all_options = {'wrapper': 'dummy'}
self.assertEqual(bobj._get_wrapper_archive_name(),
'FooBar_1.24b_20202.7z')

View File

@@ -1,66 +1,55 @@
from unittest import TestCase
from unittest import TestCase, mock
try:
from unittest import mock
except ImportError:
import mock
from fs_uae_wrapper import cd32
from fs_uae_wrapper import utils
from fs_uae_wrapper import cd32, utils
class TestCD32(TestCase):
def test_validate_options(self):
acd32 = cd32.CD32('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(acd32._validate_options())
acd32.all_options['wrapper'] = 'cd32'
self.assertFalse(acd32._validate_options())
acd32.all_options['wrapper_archive'] = 'fake.tgz'
self.assertTrue(acd32._validate_options())
@mock.patch('tempfile.mkdtemp')
@mock.patch('fs_uae_wrapper.base.Base._save_save')
@mock.patch('fs_uae_wrapper.base.Base._run_emulator')
@mock.patch('fs_uae_wrapper.base.Base._kickstart_option')
@mock.patch('fs_uae_wrapper.base.Base._load_save')
@mock.patch('fs_uae_wrapper.base.Base._copy_conf')
@mock.patch('fs_uae_wrapper.base.Base._extract')
def test_run(self, extr, cconf, lsave, kick, runemul, ssave, mkdtemp):
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_wrapper_archive_name')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._save_save')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._get_saves_dir')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._load_save')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
def test_run(self, extract, load_save, copy_conf, run_emulator,
get_save_dir, save_state, get_wrapper_arch_name, which,
mkdtemp):
extr.return_value = False
cconf.return_value = False
lsave.return_value = False
kick.return_value = {}
runemul.return_value = False
ssave.return_value = False
extract.return_value = False
copy_conf.return_value = False
load_save.return_value = False
run_emulator.return_value = False
get_save_dir.return_value = False
save_state.return_value = False
get_wrapper_arch_name.return_value = "fake_arch_filename"
which.return_value = 'unrar'
acd32 = cd32.CD32('Config.fs-uae', utils.CmdOption(), {})
acd32 = cd32.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(acd32.run())
acd32.all_options = {'wrapper': 'cd32',
'wrapper_archive': 'fake.tgz'}
'wrapper_archive': 'fake.tgz',
'wrapper_archiver': 'rar'}
self.assertFalse(acd32.run())
extr.return_value = True
extract.return_value = True
self.assertFalse(acd32.run())
cconf.return_value = True
copy_conf.return_value = True
self.assertFalse(acd32.run())
lsave.return_value = True
load_save.return_value = True
self.assertFalse(acd32.run())
run_emulator.return_value = True
self.assertTrue(acd32.run())
kick.return_value = {'foo': 'bar'}
self.assertTrue(acd32.run())
self.assertDictEqual(acd32.fsuae_options, {'foo': 'bar'})
runemul.return_value = True
get_save_dir.return_value = True
self.assertFalse(acd32.run())
ssave.return_value = True
save_state.return_value = True
self.assertTrue(acd32.run())

View File

@@ -1,12 +1,7 @@
import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase
try:
from unittest import mock
except ImportError:
import mock
from unittest import TestCase, mock
from fs_uae_wrapper import file_archive
@@ -54,75 +49,96 @@ class TestArchive(TestCase):
call.assert_called_once_with(['false', 'a', 'foo', '.'])
call.reset_mock()
call.return_value = 1
self.assertFalse(arch.extract('foo'))
with open('foo', 'w') as fobj:
fobj.write('\n')
self.assertTrue(arch.extract('foo'))
call.return_value = 1
call.reset_mock()
self.assertFalse(arch.create('foo'))
call.assert_called_once_with(['false', 'a', 'foo', '.'])
call.reset_mock()
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['false', 'x', 'foo'])
def test_archive_which(self):
self.assertEqual(file_archive.which('sh'), 'sh')
self.assertIsNone(file_archive.which('blahblahexec'))
self.assertEqual(file_archive.which(['blahblahexec', 'pip', 'sh']),
'pip')
@mock.patch('fs_uae_wrapper.file_archive.which')
@mock.patch('os.path.exists')
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('subprocess.call')
def test_tar(self, call, which):
def test_tar(self, call, which, exists):
with open('foo', 'w') as fobj:
fobj.write('\n')
which.return_value = 'tar'
exists.return_value = True
arch = file_archive.TarArchive()
arch.archiver = 'tar'
call.return_value = 0
self.assertTrue(arch.create('foo'))
call.assert_called_once_with(['tar', 'cf', 'foo', '.'])
self.assertTrue(arch.create('foo.tar'))
call.assert_called_once_with(['tar', 'cf', 'foo.tar', 'foo'])
call.reset_mock()
call.return_value = 1
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['tar', 'xf', 'foo'])
self.assertFalse(arch.extract('foo.tar'))
call.assert_called_once_with(['tar', 'xf', 'foo.tar'])
call.reset_mock()
arch = file_archive.TarGzipArchive()
arch.archiver = 'tar'
call.return_value = 0
self.assertTrue(arch.create('foo'))
call.assert_called_once_with(['tar', 'zcf', 'foo', '.'])
self.assertTrue(arch.create('foo.tgz'))
call.assert_called_once_with(['tar', 'zcf', 'foo.tgz', 'foo'])
call.reset_mock()
call.return_value = 1
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['tar', 'xf', 'foo'])
self.assertFalse(arch.extract('foo.tgz'))
call.assert_called_once_with(['tar', 'xf', 'foo.tgz'])
call.reset_mock()
arch = file_archive.TarBzip2Archive()
arch.archiver = 'tar'
call.return_value = 0
self.assertTrue(arch.create('foo'))
call.assert_called_once_with(['tar', 'jcf', 'foo', '.'])
self.assertTrue(arch.create('foo.tar.bz2'))
call.assert_called_once_with(['tar', 'jcf', 'foo.tar.bz2', 'foo'])
call.reset_mock()
call.return_value = 1
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['tar', 'xf', 'foo'])
self.assertFalse(arch.extract('foo.tar.bz2'))
call.assert_called_once_with(['tar', 'xf', 'foo.tar.bz2'])
call.reset_mock()
arch = file_archive.TarXzArchive()
arch.archiver = 'tar'
call.return_value = 0
self.assertTrue(arch.create('foo'))
call.assert_called_once_with(['tar', 'Jcf', 'foo', '.'])
self.assertTrue(arch.create('foo.tar.xz'))
call.assert_called_once_with(['tar', 'Jcf', 'foo.tar.xz', 'foo'])
call.reset_mock()
call.return_value = 1
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['tar', 'xf', 'foo'])
self.assertFalse(arch.extract('foo.tar.xz'))
call.assert_called_once_with(['tar', 'xf', 'foo.tar.xz'])
@mock.patch('fs_uae_wrapper.file_archive.which')
with open('bar', 'w') as fobj:
fobj.write('\n')
call.reset_mock()
arch = file_archive.TarGzipArchive()
arch.archiver = 'tar'
call.return_value = 0
self.assertTrue(arch.create('foo.tgz'))
call.assert_called_once_with(['tar', 'zcf', 'foo.tgz', 'bar', 'foo'])
call.reset_mock()
call.return_value = 1
arch = file_archive.TarArchive()
self.assertFalse(arch.create('foo.tar'))
call.assert_called_once_with(['tar', 'cf', 'foo.tar', 'bar', 'foo'])
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('subprocess.call')
def test_lha(self, call, which):
with open('foo', 'w') as fobj:
@@ -142,7 +158,7 @@ class TestArchive(TestCase):
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['lha', 'x', 'foo'])
@mock.patch('fs_uae_wrapper.file_archive.which')
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('subprocess.call')
def test_lzx(self, call, which):
with open('foo', 'w') as fobj:
@@ -162,7 +178,7 @@ class TestArchive(TestCase):
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['unlzx', '-x', 'foo'])
@mock.patch('fs_uae_wrapper.file_archive.which')
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('subprocess.call')
def test_7zip(self, call, which):
with open('foo', 'w') as fobj:
@@ -182,7 +198,7 @@ class TestArchive(TestCase):
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['7z', 'x', 'foo'])
@mock.patch('fs_uae_wrapper.file_archive.which')
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('subprocess.call')
def test_zip(self, call, which):
with open('foo', 'w') as fobj:
@@ -202,7 +218,12 @@ class TestArchive(TestCase):
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['7z', 'x', 'foo'])
@mock.patch('fs_uae_wrapper.file_archive.which')
which.side_effect = ['zip', 'unzip']
arch = file_archive.ZipArchive()
self.assertEqual(arch._compress, 'zip')
self.assertEqual(arch._decompress, 'unzip')
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('subprocess.call')
def test_rar(self, call, which):
@@ -225,17 +246,23 @@ class TestArchive(TestCase):
self.assertTrue(arch.create('foo.rar'))
call.assert_called_once_with(['rar', 'a', 'foo.rar', 'bar', 'baz',
'directory', 'foo'])
call.return_value = 1
call.reset_mock()
self.assertFalse(arch.create('foo.rar'))
call.assert_called_once_with(['rar', 'a', 'foo.rar', 'bar', 'baz',
'directory', 'foo'])
with open('foo', 'w') as fobj:
fobj.write('\n')
call.reset_mock()
call.return_value = 1
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['rar', 'x', 'foo'])
call.reset_mock()
call.return_value = 0
arch._compess = arch._decompess = arch.archiver = 'unrar'
arch._compress = arch._decompress = arch.archiver = 'unrar'
self.assertFalse(arch.create('foo'))
call.assert_not_called()
@@ -244,3 +271,42 @@ class TestArchive(TestCase):
call.return_value = 1
self.assertFalse(arch.extract('foo'))
call.assert_called_once_with(['unrar', 'x', 'foo'])
class TestArchivers(TestCase):
def test_get(self):
self.assertEqual(file_archive.Archivers.get('tar'),
file_archive.TarArchive)
self.assertEqual(file_archive.Archivers.get('tar.gz'),
file_archive.TarGzipArchive)
self.assertEqual(file_archive.Archivers.get('tgz'),
file_archive.TarGzipArchive)
self.assertEqual(file_archive.Archivers.get('tar.bz2'),
file_archive.TarBzip2Archive)
self.assertEqual(file_archive.Archivers.get('tar.xz'),
file_archive.TarXzArchive)
self.assertEqual(file_archive.Archivers.get('rar'),
file_archive.RarArchive)
self.assertEqual(file_archive.Archivers.get('7z'),
file_archive.SevenZArchive)
self.assertEqual(file_archive.Archivers.get('lha'),
file_archive.LhaArchive)
self.assertEqual(file_archive.Archivers.get('lzh'),
file_archive.LhaArchive)
self.assertEqual(file_archive.Archivers.get('lzx'),
file_archive.LzxArchive)
self.assertIsNone(file_archive.Archivers.get('ace'))
def test_get_extension_by_name(self):
archivers = file_archive.Archivers
self.assertEqual(archivers.get_extension_by_name('tar'), '.tar')
self.assertEqual(archivers.get_extension_by_name('tgz'), '.tar.gz')
self.assertEqual(archivers.get_extension_by_name('tar.bz2'),
'.tar.bz2')
self.assertEqual(archivers.get_extension_by_name('tar.xz'), '.tar.xz')
self.assertEqual(archivers.get_extension_by_name('rar'), '.rar')
self.assertEqual(archivers.get_extension_by_name('7z'), '.7z')
self.assertEqual(archivers.get_extension_by_name('lha'), '.lha')
self.assertEqual(archivers.get_extension_by_name('lzx'), '.lzx')
self.assertIsNone(archivers.get_extension_by_name('ace'))

View File

@@ -1,20 +1,11 @@
from unittest import TestCase
import os
try:
from unittest import mock
except ImportError:
import mock
from unittest import TestCase, mock
from fs_uae_wrapper import message
if os.environ.get('DISPLAY'):
try:
import tkinter as tk
from tkinter import ttk
except ImportError:
import Tkinter as tk
import ttk
import tkinter as tk
from tkinter import ttk
class TestMessage(TestCase):

12
tests/test_path.py Normal file
View File

@@ -0,0 +1,12 @@
from unittest import TestCase
from fs_uae_wrapper import path
class TestPath(TestCase):
def test_which(self):
self.assertEqual(path.which('sh'), 'sh')
self.assertIsNone(path.which('blahblahexec'))
self.assertEqual(path.which(['blahblahexec', 'pip', 'sh']),
'pip')

View File

@@ -1,18 +1,16 @@
from unittest import TestCase
from unittest import TestCase, mock
try:
from unittest import mock
except ImportError:
import mock
from fs_uae_wrapper import plain
from fs_uae_wrapper import utils
from fs_uae_wrapper import plain, utils
class TestPlainModule(TestCase):
@mock.patch('subprocess.call')
def test_show(self, subprocess_call):
@mock.patch('fs_uae_wrapper.utils.run_command')
def test_run(self, run_command):
wrapper = plain.Wrapper('some.conf', utils.CmdOption(), {})
wrapper.run()
run_command.assert_called_once_with(['fs-uae', 'some.conf'])
plain.run('some.conf', utils.CmdOption(), None)
subprocess_call.assert_called_once()
def test_clean(self):
wrapper = plain.Wrapper('some.conf', utils.CmdOption(), {})
self.assertIsNone(wrapper.clean())

73
tests/test_savestate.py Normal file
View File

@@ -0,0 +1,73 @@
import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase, mock
from fs_uae_wrapper import savestate, utils
class TestSaveState(TestCase):
def setUp(self):
self.dirname = mkdtemp()
self.curdir = os.path.abspath(os.curdir)
os.chdir(self.dirname)
def tearDown(self):
os.chdir(self.curdir)
try:
shutil.rmtree(self.dirname)
except OSError:
pass
@mock.patch('tempfile.mkdtemp')
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('fs_uae_wrapper.base.Base._save_save')
@mock.patch('fs_uae_wrapper.base.Base._get_saves_dir')
@mock.patch('fs_uae_wrapper.base.Base._run_emulator')
@mock.patch('fs_uae_wrapper.base.Base._copy_conf')
@mock.patch('fs_uae_wrapper.base.Base._load_save')
def test_run(self, load_save, copy_conf, run_emulator, get_save_dir,
save_state, which, mkdtemp):
copy_conf.return_value = False
run_emulator.return_value = False
get_save_dir.return_value = False
save_state.return_value = False
which.return_value = 'rar'
arch = savestate.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(arch.run())
arch.all_options = {'wrapper': 'savestate',
'wrapper_save_state': '1',
'wrapper_archiver': 'rar'}
self.assertFalse(arch.run())
self.assertFalse(arch.run())
copy_conf.return_value = True
self.assertFalse(arch.run())
run_emulator.return_value = True
self.assertTrue(arch.run())
get_save_dir.return_value = True
self.assertFalse(arch.run())
save_state.return_value = True
self.assertTrue(arch.run())
@mock.patch('fs_uae_wrapper.path.which')
def test_validate_options(self, which):
which.return_value = 'unrar'
arch = savestate.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(arch._validate_options())
arch.all_options['wrapper'] = 'savestate'
self.assertTrue(arch._validate_options())
arch.all_options['wrapper_archiver'] = 'rar'
self.assertTrue(arch._validate_options())

View File

@@ -1,13 +1,8 @@
import os
import sys
from tempfile import mkstemp, mkdtemp
from unittest import TestCase
import shutil
try:
from unittest import mock
except ImportError:
import mock
import sys
from tempfile import mkdtemp, mkstemp
from unittest import TestCase, mock
from fs_uae_wrapper import utils
@@ -72,84 +67,84 @@ class TestUtils(TestCase):
conf = utils.get_config_options(self.fname)
self.assertDictEqual(conf, {'wrapper': ''})
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('fs_uae_wrapper.file_archive.Archive.extract')
@mock.patch('fs_uae_wrapper.file_archive.Archive.create')
@mock.patch('fs_uae_wrapper.message.Message.close')
@mock.patch('fs_uae_wrapper.message.Message.show')
def test_operate_archive(self, show, close, create, extract):
def test_operate_archive(self, show, close, create, extract, which):
os.chdir(self.dirname)
which.return_value = None
# No config
self.assertFalse(utils.operate_archive('non-existend.7z', 'foo', ''))
self.assertFalse(utils.operate_archive('non-existent.7z', 'foo', '',
None))
# Archive type not known
with open('unsupported-archive.ace', 'w') as fobj:
fobj.write("\n")
self.assertFalse(utils.operate_archive('unsupported-archive.ace',
'foo', ''))
'foo', '', None))
# archive is known, but extraction will fail - we have an empty
# archive and there is no guarantee, that 7z exists on system where
# test will run
which.return_value = '7z'
extract.return_value = True
with open('supported-archive.7z', 'w') as fobj:
fobj.write("\n")
self.assertTrue(utils.operate_archive('supported-archive.7z',
'extract', ''))
'extract', '', None))
extract.assert_called_once()
extract.reset_mock()
self.assertTrue(utils.operate_archive('supported-archive.7z',
'extract', ''))
'extract', '', None))
extract.assert_called_once()
os.unlink('supported-archive.7z')
self.assertTrue(utils.operate_archive('supported-archive.7z',
'create', 'test'))
'create', 'test', ['foo']))
create.assert_called_once()
show.assert_called_once()
def test_extract_archive(self):
@mock.patch('fs_uae_wrapper.utils.operate_archive')
def test_extract_archive(self, operate):
os.chdir(self.dirname)
# No config
self.assertFalse(utils.extract_archive('non-existend.7z'))
operate.return_value = True
self.assertTrue(utils.extract_archive('arch.7z'))
operate.assert_called_once_with('arch.7z', 'extract', '', None)
# Archive type not known
with open('unsupported-archive.ace', 'w') as fobj:
fobj.write("\n")
self.assertFalse(utils.extract_archive('unsupported-archive.ace'))
operate.reset_mock()
operate.return_value = False
self.assertFalse(utils.extract_archive('arch.7z', 'MyFoo',
['foo', 'bar']))
operate.assert_called_once_with('arch.7z', 'extract',
"Extracting files for `MyFoo'. Please"
" be patient", ['foo', 'bar'])
# archive is known, but extraction will fail - we have an empty
# archive and there is no guarantee, that 7z exists on system where
# test will run
with open('supported-archive.7z', 'w') as fobj:
fobj.write("\n")
self.assertFalse(utils.extract_archive('supported-archive.7z'))
@mock.patch('fs_uae_wrapper.utils.operate_archive')
def test_create_archive(self, operate):
operate.return_value = True
self.assertTrue(utils.create_archive('arch.7z'))
operate.assert_called_once_with('arch.7z', 'create', '', None)
def test_create_archive(self):
os.chdir(self.dirname)
# No config
self.assertFalse(utils.extract_archive('non-existend.7z'))
# Archive type not known
with open('unsupported-archive.ace', 'w') as fobj:
fobj.write("\n")
self.assertFalse(utils.extract_archive('unsupported-archive.ace'))
# archive is known, but extraction will fail - we have an empty
# archive and there is no guarantee, that 7z exists on system where
# test will run
with open('supported-archive.7z', 'w') as fobj:
fobj.write("\n")
self.assertFalse(utils.extract_archive('supported-archive.7z'))
operate.reset_mock()
operate.return_value = False
self.assertFalse(utils.create_archive('arch.7z', 'MyFoo',
['foo', 'bar']))
operate.assert_called_once_with('arch.7z', 'create',
"Creating archive for `MyFoo'. Please"
" be patient", ['foo', 'bar'])
@mock.patch('fs_uae_wrapper.path.which')
@mock.patch('fs_uae_wrapper.file_archive.Archive.extract')
def test_extract_archive_positive(self, arch_extract):
def test_extract_archive_positive(self, arch_extract, which):
arch_extract.return_value = True
which.return_value = '7z'
os.chdir(self.dirname)
# archive is known, and extraction should succeed
@@ -170,6 +165,37 @@ class TestUtils(TestCase):
self.assertDictEqual(conf, {'foo': '1', 'bar': 'zip'})
self.assertDictEqual(other, {'foo': '2', 'baz': '3'})
@mock.patch('subprocess.call')
def test_run_command(self, call):
call.return_value = 0
self.assertTrue(utils.run_command(['ls']))
call.assert_called_once_with(['ls'])
call.reset_mock()
self.assertTrue(utils.run_command('ls -l'))
call.assert_called_once_with(['ls', '-l'])
call.return_value = 1
call.reset_mock()
self.assertFalse(utils.run_command(['ls', '-l']))
call.assert_called_once_with(['ls', '-l'])
call.reset_mock()
self.assertFalse(utils.run_command('ls'))
call.assert_called_once_with(['ls'])
@mock.patch('os.path.exists')
def test_get_config(self, exists):
exists.return_value = False
os.chdir(self.dirname)
self.assertDictEqual(utils.get_config('foo'), {})
with open('conf.fs-uae', 'w') as fobj:
fobj.write("[conf]\nwrapper=foo\n")
self.assertDictEqual(utils.get_config('conf.fs-uae'),
{'wrapper': 'foo'})
class TestCmdOptions(TestCase):
@@ -203,33 +229,43 @@ class TestCmdOptions(TestCase):
self.assertListEqual(sorted(cmd.list()),
['--fast_memory=4096', '--fullscreen'])
@mock.patch('os.path.exists')
@mock.patch('os.getenv')
@mock.patch('os.path.expandvars')
@mock.patch('distutils.spawn.find_executable')
def test_interpolate_variables(self, find_exe, expandv, getenv):
@mock.patch('shutil.which')
def test_interpolate_variables(self, which, expandv, getenv, os_exists):
os_exists.return_value = True
itrpl = utils.interpolate_variables
string = 'foo = $CONFIG/../path/to/smth'
string = '$CONFIG/../path/to/smth'
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
'foo = /home/user/../path/to/smth')
string = 'bar = $HOME'
'/home/path/to/smth')
string = '$HOME'
expandv.return_value = '/home/user'
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
'bar = /home/user')
'/home/user')
string = 'foo = $APP/$EXE'
find_exe.return_value = '/usr/bin/fs-uae'
string = '$APP/$EXE'
which.return_value = '/usr/bin/fs-uae'
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
'foo = /usr/bin/fs-uae//usr/bin/fs-uae')
'/usr/bin/fs-uae//usr/bin/fs-uae')
string = 'docs = $DOCUMENTS'
string = '$DOCUMENTS'
getenv.return_value = '/home/user/Docs'
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
'docs = /home/user/Docs')
'/home/user/Docs')
string = 'baz = $BASE'
string = '$BASE'
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'),
'baz = $BASE')
'$BASE')
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae', 'base'),
'baz = base')
'base')
@mock.patch('os.getenv')
@mock.patch('os.path.expandvars')
def test_interpolate_variables_path_not_exists(self, expandv, getenv):
itrpl = utils.interpolate_variables
string = '$CONFIG/../path/to/smth'
self.assertEqual(itrpl(string, '/home/user/Config.fs-uae'), string)

233
tests/test_whdload.py Normal file
View File

@@ -0,0 +1,233 @@
import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase, mock
from fs_uae_wrapper import utils, whdload
class TestWHDLoad(TestCase):
def setUp(self):
self.dirname = mkdtemp()
self.curdir = os.path.abspath(os.curdir)
os.chdir(self.dirname)
def tearDown(self):
os.chdir(self.curdir)
try:
shutil.rmtree(self.dirname)
except OSError:
pass
@mock.patch('fs_uae_wrapper.base.ArchiveBase._validate_options')
def test_validate_options_arch_validation_fail(self, base_valid):
base_valid.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper._validate_options())
@mock.patch('fs_uae_wrapper.base.ArchiveBase._validate_options')
def test_validate_options_no_base_image(self, base_valid):
base_valid.return_value = True
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper._validate_options())
@mock.patch('fs_uae_wrapper.base.ArchiveBase._validate_options')
def test_validate_options_with_base_image_set(self, base_valid):
base_valid.return_value = True
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.all_options['wrapper_whdload_base'] = 'fake_base_fname.7z'
self.assertTrue(wrapper._validate_options())
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
def test_run_base_run_fail(self, run):
run.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper.run())
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
def test_run_extract_fail(self, run, extract):
run.return_value = True
extract.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.all_options = {'wrapper': 'whdload',
'wrapper_archive': 'fake.tgz',
'wrapper_archiver': 'rar'}
self.assertFalse(wrapper.run())
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
def test_run_copy_conf_fail(self, run, extract, copy_conf):
run.return_value = True
extract.return_value = True
copy_conf.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper.run())
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
def test_run_emulator_fail(self, run, extract, copy_conf, run_emulator):
run.return_value = True
extract.return_value = True
copy_conf.return_value = True
run_emulator.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper.run())
@mock.patch('fs_uae_wrapper.base.ArchiveBase._run_emulator')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._copy_conf')
@mock.patch('fs_uae_wrapper.whdload.Wrapper._extract')
@mock.patch('fs_uae_wrapper.base.ArchiveBase.run')
def test_run_success(self, run, extract, copy_conf, run_emulator):
run.return_value = True
extract.return_value = True
copy_conf.return_value = True
run_emulator.return_value = True
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertTrue(wrapper.run())
@mock.patch('os.path.exists')
def test_extract_nonexistent_image(self, exists):
exists.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
self.assertFalse(wrapper._extract())
@mock.patch('os.chdir')
@mock.patch('os.path.exists')
def test_extract_extraction_failed(self, exists, chdir):
exists.return_value = True
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename.7z'
self.assertFalse(wrapper._extract())
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
@mock.patch('fs_uae_wrapper.utils.extract_archive')
@mock.patch('os.chdir')
@mock.patch('os.path.exists')
def test_extract_extraction_of_whdload_arch_failed(self, exists, chdir,
image_extract,
arch_extract):
exists.return_value = True
image_extract.return_value = True
arch_extract.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
self.assertFalse(wrapper._extract())
@mock.patch('fs_uae_wrapper.whdload.Wrapper._find_slave')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
@mock.patch('fs_uae_wrapper.utils.extract_archive')
@mock.patch('os.chdir')
@mock.patch('os.path.exists')
def test_extract_slave_not_found(self, exists, chdir, image_extract,
arch_extract, find_slave):
exists.return_value = True
image_extract.return_value = True
arch_extract.return_value = True
find_slave.return_value = False
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
self.assertFalse(wrapper._extract())
@mock.patch('fs_uae_wrapper.whdload.Wrapper._find_slave')
@mock.patch('fs_uae_wrapper.base.ArchiveBase._extract')
@mock.patch('fs_uae_wrapper.utils.extract_archive')
@mock.patch('os.chdir')
@mock.patch('os.path.exists')
def test_extract_success(self, exists, chdir, image_extract, arch_extract,
find_slave):
exists.return_value = True
image_extract.return_value = True
arch_extract.return_value = True
find_slave.return_value = True
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
wrapper.fsuae_options['wrapper_whdload_base'] = 'fakefilename'
self.assertTrue(wrapper._extract())
@mock.patch('os.walk')
@mock.patch('os.chdir')
def test_find_slave_no_slave_file(self, chdir, walk):
walk.return_value = [(".", ('game'), ()),
('./game', (), ('foo', 'bar', 'baz'))]
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper._find_slave())
@mock.patch('os.listdir')
@mock.patch('os.walk')
@mock.patch('os.chdir')
def test_find_slave_no_corresponding_icon(self, chdir, walk, listdir):
contents = ('foo', 'bar', 'baz.slave')
walk.return_value = [(".", ('game'), ()),
('./game', (), contents)]
listdir.return_value = contents
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
self.assertFalse(wrapper._find_slave())
@mock.patch('os.listdir')
@mock.patch('os.walk')
@mock.patch('os.chdir')
def test_find_slave_success(self, chdir, walk, listdir):
contents = ('foo', 'bar', 'baz.slave', 'baz.info')
_open = mock.mock_open()
walk.return_value = [(".", ('C', 'S', 'game'), ()),
('./C', (), ('Assign', 'kgiconload')),
('./S', (), ()),
('./game', (), contents)]
listdir.return_value = contents
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
with mock.patch('builtins.open', _open):
self.assertTrue(wrapper._find_slave())
handle = _open()
handle.write.assert_called_once_with('cd game\n'
'C:kgiconload baz.info\n')
@mock.patch('os.listdir')
@mock.patch('os.walk')
@mock.patch('os.chdir')
def test_find_slave_minial(self, chdir, walk, listdir):
contents = ('foo', 'bar', 'baz.slave', 'baz.info')
_open = mock.mock_open()
walk.return_value = [(".", ('C', 'S', 'game'), ()),
('./C', (), ('Assign', 'WHDLoad')),
('./S', (), ()),
('./game', (), contents)]
listdir.return_value = contents
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
with mock.patch('builtins.open', _open):
self.assertTrue(wrapper._find_slave())
handle = _open()
handle.write.assert_called_once_with('cd game\nC:whdload Preload '
'Slave=baz.slave\n')
@mock.patch('os.listdir')
@mock.patch('os.walk')
@mock.patch('os.chdir')
def test_find_custom_options(self, chdir, walk, listdir):
contents = ('foo', 'bar', 'baz.slave', 'baz.info')
_open = mock.mock_open()
walk.return_value = [(".", ('C', 'S', 'game'), ()),
('./C', (), ('Assign', 'WHDLoad')),
('./S', (), ()),
('./game', (), contents)]
listdir.return_value = contents
wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {})
whdl_opts = 'Preload SplashDelay=0 MMU PAL'
wrapper.all_options['wrapper_whdload_options'] = whdl_opts
with mock.patch('builtins.open', _open):
self.assertTrue(wrapper._find_slave())
handle = _open()
handle.write.assert_called_once_with(f'cd game\nC:whdload {whdl_opts} '
'Slave=baz.slave\n')

View File

@@ -1,13 +1,8 @@
import os
import sys
from tempfile import mkstemp, mkdtemp
from unittest import TestCase
import shutil
try:
from unittest import mock
except ImportError:
import mock
import sys
from tempfile import mkdtemp, mkstemp
from unittest import TestCase, mock
from fs_uae_wrapper import wrapper
@@ -28,7 +23,7 @@ class TestWrapper(TestCase):
os.unlink(self.fname)
sys.argv = self._argv[:]
@mock.patch('fs_uae_wrapper.plain.run')
@mock.patch('fs_uae_wrapper.plain.Wrapper.run')
def test_run(self, mock_plain_run):
sys.argv.append('--help')
@@ -47,10 +42,7 @@ class TestWrapper(TestCase):
fobj.write('\n')
wrapper.run()
mock_plain_run.called_once_with('Config.fs-uae',
['--fullscreen',
'--fade_out_duration=0'],
[])
mock_plain_run.assert_called_once()
# This will obviously fail for nonexistent module
sys.argv.append('--wrapper=dummy_wrapper')
@@ -67,11 +59,13 @@ class TestWrapper(TestCase):
def test_parse_args(self):
# Looking for configuration file... first, we have nothing
self.assertEqual(wrapper.parse_args(), (None, {}))
self.assertEqual(wrapper.parse_args(),
(None, {'wrapper_verbose': 0, 'wrapper_quiet': 0}))
# still no luck - nonexistent file
sys.argv.append('there-is-no-config.fs-uae')
self.assertEqual(wrapper.parse_args(), (None, {}))
self.assertEqual(wrapper.parse_args(),
(None, {'wrapper_verbose': 0, 'wrapper_quiet': 0}))
# lets make it
os.chdir(self.dirname)
@@ -79,7 +73,8 @@ class TestWrapper(TestCase):
fobj.write('\n')
self.assertEqual(wrapper.parse_args(),
('there-is-no-config.fs-uae', {}))
('there-is-no-config.fs-uae',
{'wrapper_verbose': 0, 'wrapper_quiet': 0}))
# remove argument, try to find default one
sys.argv.pop()
@@ -88,7 +83,9 @@ class TestWrapper(TestCase):
with open('Config.fs-uae', 'w') as fobj:
fobj.write('\n')
self.assertEqual(wrapper.parse_args(), ('Config.fs-uae', {}))
self.assertEqual(wrapper.parse_args(),
('Config.fs-uae',
{'wrapper_verbose': 0, 'wrapper_quiet': 0}))
# add --wrapper-foo and --wrapper-bar options
sys.argv.extend(['--wrapper=plain', '--wrapper_foo=1',
@@ -104,7 +101,9 @@ class TestWrapper(TestCase):
self.assertEqual(conf, 'Config.fs-uae')
self.assertDictEqual(fsopts, {'wrapper': 'plain',
'wrapper_foo': '1',
'wrapper_bar': 'false'})
'wrapper_bar': 'false',
'wrapper_verbose': 0,
'wrapper_quiet': 0})
# mix wrapper* params in commandline and config
sys.argv = ['fs-uae-wrapper',
@@ -120,4 +119,6 @@ class TestWrapper(TestCase):
self.assertDictEqual(fsopts, {'wrapper': 'plain',
'wrapper_bar': 'false',
'fullscreen': '1',
'fast_memory': '4096'})
'fast_memory': '4096',
'wrapper_verbose': 0,
'wrapper_quiet': 0})

22
tox.ini
View File

@@ -1,5 +1,5 @@
[tox]
envlist = py27,py34,py27-flake8,py34-flake8
envlist = py3,py3-flake8
usedevelop = True
@@ -8,18 +8,14 @@ usedevelop=True
setenv = COVERAGE_FILE = .coverage
commands = py.test --cov=fs_uae_wrapper --cov-report=term-missing
deps = -r{toxinidir}/test-requirements.txt
deps =
pytest
pytest-cov
pytest-pep8
coverage
flake8
[testenv:py27]
deps = {[testenv]deps}
mock
[testenv:py34-flake8]
basepython = python3.4
deps = flake8
commands = flake8 {posargs}
[testenv:py27-flake8]
basepython = python2.7
[testenv:py3-flake8]
basepython = python3
deps = flake8
commands = flake8 {posargs}