├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── appveyor.yml ├── codecov.yml ├── doc ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── api │ ├── console_reader.rst │ ├── host.rst │ ├── index.rst │ └── wexpect_util.rst │ ├── conf.py │ ├── examples.rst │ ├── history.rst │ └── index.rst ├── examples ├── README.md ├── cmd_wrapper.py ├── foo.py ├── hello_wexpect.py ├── python.py └── termination.py ├── issues ├── README.md ├── bla.py ├── bla2.py ├── i09_cmd_error.py ├── i09_confused_string.py ├── i09_py_sim_no_error.py ├── i09_py_sim_no_error2.py ├── i10_parent.py ├── i11_greek_parent.py ├── i11_greek_printer.py ├── i11_unicode_parent.py ├── i11_unicode_printer.py ├── i18_setecho_error.py └── long_printer.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── PexpectTestCase.py ├── __init__.py ├── echo_w_prompt.py ├── exit1.py ├── foo.py ├── lines_printer.py ├── list100.py ├── long_printer.py ├── needs_kill.py ├── parametric_printer.py ├── pyinstaller_test.py ├── test_command_list_split.py ├── test_console_reader.py ├── test_constructor.py ├── test_delay.py ├── test_destructor.py ├── test_echo.py ├── test_expect.py ├── test_interact.py ├── test_isalive.py ├── test_long.py ├── test_misc.py ├── test_misc_console_coverage.py ├── test_parametric_printer.py ├── test_parametric_printer_coverage.py ├── test_readline.py ├── test_run.py ├── test_timeout_pattern.py └── utils.py ├── tox.ini ├── wexpect.spec └── wexpect ├── __init__.py ├── __main__.py ├── console_reader.py ├── host.py ├── legacy_wexpect.py └── wexpect_util.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Environment:** 23 | - [windows version] 24 | - [Python version] 25 | - [wexpect version] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test_01_installed/* 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | *coverage.xml 8 | 9 | # C extensions 10 | *.so 11 | 12 | .eggs/ 13 | AUTHORS 14 | ChangeLog 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | !wexpect.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ 64 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 3 | # 4 | # Travis-ci doesn't supports python runs on windows platform right now (2019.09.09). This medium story 5 | # https://medium.com/@dirk.avery/travis-ci-python-and-windows-2f9a1b6dd096 can didn't helped either. 6 | # (wexpect is a compicated case) 7 | # 8 | # So travis build paused! See appveyor builds. (https://ci.appveyor.com/project/raczben/wexpect) 9 | 10 | language: python 11 | dist: xenial # required for Python >= 3.7 12 | matrix: 13 | include: 14 | - stage: test 15 | - os: windows 16 | language: sh 17 | python: "3.7" 18 | before_install: 19 | - choco install python3 20 | - export PATH="/c/Python37:/c/Python37/Scripts:$PATH" 21 | - python -m pip install virtualenv 22 | - ls -la /c/Python37/Scripts 23 | - virtualenv $HOME/venv 24 | - source $HOME/venv/Scripts/activate 25 | # command to install dependencies 26 | install: 27 | - pip install .[test] 28 | # command to run tests 29 | script: 30 | - tox 31 | # Push the results back to codecov 32 | after_success: 33 | - codecov 34 | 35 | notifications: 36 | email: 37 | on_success: never 38 | on_failure: always 39 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at betontalpfa@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2008 Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, Benedek Racz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **wexpect** 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/tbji72d5s0tagrt9?svg=true)](https://ci.appveyor.com/project/raczben/wexpect) 4 | [![codecov](https://codecov.io/gh/raczben/wexpect/branch/master/graph/badge.svg)](https://codecov.io/gh/raczben/wexpect) 5 | [![Documentation Status](https://readthedocs.org/projects/wexpect/badge/?version=latest)](https://wexpect.readthedocs.io/en/latest/?badge=latest) 6 | 7 | *Wexpect* is a Windows variant of [pexpect](https://pexpect.readthedocs.io/en/stable/). 8 | 9 | *Pexpect* is a Python module for spawning child applications and controlling 10 | them automatically. 11 | 12 | ## You need wexpect if... 13 | 14 | - you want to control any windows console application from python script. 15 | - you want to write test-automation script for a windows console application. 16 | - you want to automate your job by controlling multiple application parallel, synchronously. 17 | 18 | ## **Install** 19 | 20 | pip install wexpect 21 | 22 | ## **Usage** 23 | 24 | To interract with a child process use `spawn` method: 25 | 26 | ```python 27 | import wexpect 28 | 29 | prompt = '[A-Z]\:.+>' 30 | 31 | child = wexpect.spawn('cmd.exe') 32 | child.expect(prompt) # Wait for startup prompt 33 | 34 | child.sendline('dir') # List the current directory 35 | child.expect(prompt) 36 | 37 | print(child.before) # Print the list 38 | child.sendline('exit') 39 | ``` 40 | 41 | For more information see [examples](./examples) folder. 42 | 43 | --- 44 | ## REFACTOR 45 | 46 | **Refactor has been finished!!!** The default spawn class is `SpawnPipe` from now. For more 47 | information read [history](https://wexpect.readthedocs.io/en/latest/history.html#refactor). 48 | 49 | --- 50 | ## What is wexpect? 51 | 52 | Wexpect is a Python module for spawning child applications and controlling 53 | them automatically. Wexpect can be used for automating interactive applications 54 | such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup 55 | scripts for duplicating software package installations on different servers. It 56 | can be used for automated software testing. Wexpect is in the spirit of Don 57 | Libes' Expect, but Wexpect is pure Python. Other Expect-like modules for Python 58 | require TCL and Expect or require C extensions to be compiled. Wexpect does not 59 | use C, Expect, or TCL extensions. 60 | 61 | Original Pexpect should work on any platform that supports the standard Python pty module. While 62 | Wexpect works on Windows platforms. The Wexpect interface focuses on ease of use so that simple 63 | tasks are easy. 64 | 65 | --- 66 | ## Dev 67 | 68 | Thanks for any contributing! 69 | 70 | ### Test 71 | 72 | To run test, enter into the folder of the wexpect's repo then: 73 | 74 | `python -m unittest` 75 | 76 | ### Deploy 77 | 78 | The deployment itself is automated and done by [appveyor](https://ci.appveyor.com/project/raczben/wexpect). 79 | See `after_test` section in [appveyor.yml](appveyor.yml) for more details. 80 | 81 | The wexpect uses [pbr](https://docs.openstack.org/pbr/latest/) for managing releasing procedures. 82 | The versioning is handled by the pbr. The *"master-version"* is the git tag. Pbr derives the package 83 | version from the git tags. 84 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # appveyor.yml 2 | --- 3 | 4 | environment: 5 | # Encrypted password (more precisely: token) for upload distro to pypy: 6 | # The "pypipw" is a custom name. 7 | # The original (sensitive) token is generated by the pypi: https://pypi.org/manage/account/token/ 8 | # The token has encripted by the appveyor: https://ci.appveyor.com/tools/encrypt 9 | pypipw: 10 | secure: N+tYP6JrVCZ12LX7MUmHYJ8kx07F4A1hXtmEW01RmhrrQBdylIf0SO/eEfW5f4ox3S4xG/lgGSzNlZckvgifrsYeeBkWwoSH5/AJ3SnOJ7HBVojVt2t3bAznS6x3aPT7WDpwGN7piwus9aHSmpKaOzRoEOBKfgHv3aUzb907C0d0Yr12LU/4cIoTAn7jMziifSq45Z50lsQwzYic/VkarxTh+GXuCCm1Mb8F686H8i6Smm1Q1n9BsXowYnzwdrTZSBVOUtpd48Mh9JKgSNhfmQ== 11 | testpypipw: 12 | secure: CcyBI8e/2LdIT2aYIytTAgR4795DNBDM/ztsz1kqZYYOeNc3zlJWLdYWrnjCHn5W6/ZcAHrsxCdCMHvtr6PIVgBRpl2RR3fk2jKTzKqJJsLW871q30BsE0kws32f1IiqfjVtLn8BUC91IJ2xBBXtOYktf1tCMi3zJMSF9+MIOQKIu298bIRnD1Lc+4lzcSZJOn4I7dOMdzlcCMRqhtO58TGwR/hD+22FHjyWVB8nLL18AO+XXS9lHSOUrH6rD5NYvVFZD68oV/RrCGAjRmfMnw== 13 | # Set default pytohn, the matrinx is in the tox settings. 14 | PYTHON: "C:\\Python37" 15 | 16 | build: on 17 | 18 | build_script: 19 | # Create source distribution. 20 | - cmd: | 21 | "%PYTHON%/python.exe" -m pip install --upgrade pip 22 | "%PYTHON%/python.exe" -m pip install --upgrade setuptools wheel 23 | "%PYTHON%/python.exe" -m setup sdist 24 | 25 | install: 26 | - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% 27 | - pip install .[test] 28 | 29 | test_script: 30 | - tox 31 | 32 | after_test: 33 | # Upload code coverage results. 34 | # https://github.com/codecov/codecov-python/issues/158#issuecomment-514282362 35 | - for %%f in (*coverage.xml) do ( 36 | (codecov --no-color -X gcov --file %%f --required ) || (sleep 30 && codecov --no-color -X gcov --file %%f --required ) 37 | ) 38 | 39 | # fill .pypirc file. 40 | # pypi 41 | - cmd: "echo [pypi] > %USERPROFILE%\\.pypirc" 42 | - cmd: "echo repository: https://upload.pypi.org/legacy/ >> %USERPROFILE%\\.pypirc" 43 | - cmd: "echo username: __token__ >> %USERPROFILE%\\.pypirc" 44 | - cmd: "echo password: %pypipw% >> %USERPROFILE%\\.pypirc" 45 | # testpypi 46 | - cmd: "echo [testpypi] >> %USERPROFILE%\\.pypirc" 47 | - cmd: "echo repository: https://test.pypi.org/legacy/ >> %USERPROFILE%\\.pypirc" 48 | - cmd: "echo username: __token__ >> %USERPROFILE%\\.pypirc" 49 | - cmd: "echo password: %testpypipw% >> %USERPROFILE%\\.pypirc" 50 | 51 | artifacts: 52 | - path: dist\wexpect*.tar.gz 53 | name: wexpect-source-distro 54 | 55 | deploy_script: 56 | # Upload to pypi. 57 | # More precisely. The master and release builds will be uploaded to pypi. Test branch will be 58 | # uploaded to test-pypi the test builds. 59 | # See more at https://stackoverflow.com/a/39155147/2506522 60 | 61 | - IF %APPVEYOR_REPO_BRANCH%==master ( 62 | twine upload -r pypi dist\\wexpect*.tar.gz 63 | ) 64 | - IF %APPVEYOR_REPO_TAG%==true ( 65 | twine upload -r pypi dist\\wexpect*.tar.gz 66 | ) 67 | - IF %APPVEYOR_REPO_BRANCH%==test ( 68 | twine upload -r testpypi dist\\wexpect*.tar.gz 69 | ) 70 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # Settings for https://codecov.io/gh/raczben/wexpect/ 2 | 3 | ignore: 4 | - tests/* # ignore folders and all its contents 5 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | pbr 2 | -------------------------------------------------------------------------------- /doc/source/api/console_reader.rst: -------------------------------------------------------------------------------- 1 | Console reader 2 | ============== 3 | 4 | .. automodule:: wexpect.console_reader 5 | 6 | 7 | ConsoleReaderPipe 8 | ----------------- 9 | 10 | .. autoclass:: ConsoleReaderPipe 11 | 12 | .. automethod:: __init__ 13 | .. automethod:: read_loop 14 | .. automethod:: suspend_child 15 | .. automethod:: resume_child 16 | .. automethod:: refresh_console 17 | .. automethod:: terminate_child 18 | .. automethod:: isalive 19 | .. automethod:: write 20 | .. automethod:: createKeyEvent 21 | .. automethod:: initConsole 22 | .. automethod:: parseData 23 | .. automethod:: getConsoleOut 24 | .. automethod:: getCoord 25 | .. automethod:: getOffset 26 | .. automethod:: readConsole 27 | .. automethod:: readConsoleToCursor 28 | .. automethod:: interact 29 | .. automethod:: sendeof 30 | .. automethod:: create_connection 31 | .. automethod:: close_connection 32 | .. automethod:: send_to_host 33 | .. automethod:: get_from_host 34 | 35 | 36 | ConsoleReaderSocket 37 | ------------------- 38 | 39 | .. autoclass:: ConsoleReaderSocket 40 | 41 | .. automethod:: __init__ 42 | .. automethod:: read_loop 43 | .. automethod:: suspend_child 44 | .. automethod:: resume_child 45 | .. automethod:: refresh_console 46 | .. automethod:: terminate_child 47 | .. automethod:: isalive 48 | .. automethod:: write 49 | .. automethod:: createKeyEvent 50 | .. automethod:: initConsole 51 | .. automethod:: parseData 52 | .. automethod:: getConsoleOut 53 | .. automethod:: getCoord 54 | .. automethod:: getOffset 55 | .. automethod:: readConsole 56 | .. automethod:: readConsoleToCursor 57 | .. automethod:: interact 58 | .. automethod:: sendeof 59 | .. automethod:: create_connection 60 | .. automethod:: close_connection 61 | .. automethod:: send_to_host 62 | .. automethod:: get_from_host 63 | -------------------------------------------------------------------------------- /doc/source/api/host.rst: -------------------------------------------------------------------------------- 1 | Host 2 | ============= 3 | 4 | .. automodule:: wexpect.host 5 | 6 | Functions 7 | --------- 8 | 9 | .. automethod:: wexpect.host.run 10 | 11 | SpawnPipe 12 | --------- 13 | 14 | .. autoclass:: SpawnPipe 15 | 16 | .. automethod:: __init__ 17 | .. automethod:: expect 18 | .. automethod:: expect_exact 19 | .. automethod:: expect_list 20 | .. automethod:: compile_pattern_list 21 | .. automethod:: send 22 | .. automethod:: sendline 23 | .. automethod:: write 24 | .. automethod:: writelines 25 | .. automethod:: sendeof 26 | .. automethod:: read 27 | .. automethod:: readline 28 | .. automethod:: read_nonblocking 29 | 30 | 31 | SpawnSocket 32 | ----------- 33 | 34 | .. autoclass:: SpawnSocket 35 | 36 | .. automethod:: __init__ 37 | .. automethod:: expect 38 | .. automethod:: expect_exact 39 | .. automethod:: expect_list 40 | .. automethod:: compile_pattern_list 41 | .. automethod:: send 42 | .. automethod:: sendline 43 | .. automethod:: write 44 | .. automethod:: writelines 45 | .. automethod:: sendeof 46 | .. automethod:: read 47 | .. automethod:: readline 48 | .. automethod:: read_nonblocking 49 | -------------------------------------------------------------------------------- /doc/source/api/index.rst: -------------------------------------------------------------------------------- 1 | API documentation 2 | ================= 3 | 4 | Wexpect symbols 5 | --------------- 6 | 7 | Wexpect package has the following symbols. (Exported by :code:`__all__` in code:`__init__.py`) 8 | 9 | .. _wexpect.spawn: 10 | 11 | **spawn** 12 | 13 | This is the main class interface for Wexpect. Use this class to start and control child applications. 14 | There are two implementation: :class:`wexpect.host.SpawnPipe` uses Windows-Pipe for communicate child. 15 | :class:`wexpect.SpawnSocket` uses TCP socket. Choose the default implementation with 16 | :code:`WEXPECT_SPAWN_CLASS` environment variable, or the :class:`wexpect.host.SpawnPipe` will be 17 | chosen by default. 18 | 19 | .. _wexpect.SpawnPipe: 20 | 21 | **SpawnPipe** 22 | 23 | :class:`wexpect.host.SpawnPipe` is the default spawn class, but you can access it directly with its 24 | exact name. 25 | 26 | .. _wexpect.SpawnSocket: 27 | 28 | **SpawnSocket** 29 | 30 | :class:`wexpect.host.SpawnSocket` is the secondary spawn class, you can access it directly with its 31 | exact name or by setting the :code:`WEXPECT_SPAWN_CLASS` environment variable to :code:`SpawnSocket` 32 | 33 | .. _wexpect.run: 34 | 35 | **run** 36 | 37 | :meth:`wexpect.host.run` runs the given command; waits for it to finish; then returns all output as a string. 38 | This function is similar to :code:`os.system()`. 39 | 40 | .. _wexpect.EOF: 41 | 42 | **EOF** 43 | 44 | :class:`wexpect.wexpect_util.EOF` is an exception. This usually means the child has exited. 45 | 46 | .. _wexpect.TIMEOUT: 47 | 48 | **TIMEOUT** 49 | 50 | :class:`wexpect.wexpect_util.TIMEOUT` raised when a read time exceeds the timeout. 51 | 52 | .. _wexpect.__version__: 53 | 54 | **__version__** 55 | 56 | This gives back the version of the wexpect release. Versioning is handled by the 57 | `pbr `_ package, which derives it from Git tags. 58 | 59 | .. _wexpect.spawn_class_name: 60 | 61 | **spawn_class_name** 62 | 63 | Contains the default spawn class' name even if the user has not specified it. The value can be 64 | :code:`SpawnPipe` or :code:`SpawnSocket` 65 | 66 | .. _wexpect.ConsoleReaderSocket: 67 | 68 | **ConsoleReaderSocket** 69 | 70 | For advanced users only! 71 | :class:`wexpect.console_reader.ConsoleReaderSocket` 72 | 73 | .. _wexpect.ConsoleReaderPipe: 74 | 75 | **ConsoleReaderPipe** 76 | 77 | For advanced users only! 78 | :class:`wexpect.console_reader.ConsoleReaderPipe` 79 | 80 | Wexpect modules 81 | --------------- 82 | 83 | .. toctree:: 84 | :maxdepth: 2 85 | 86 | host 87 | wexpect_util 88 | console_reader 89 | -------------------------------------------------------------------------------- /doc/source/api/wexpect_util.rst: -------------------------------------------------------------------------------- 1 | Wexpect util 2 | ============ 3 | 4 | .. automodule:: wexpect.wexpect_util 5 | 6 | Functions 7 | --------- 8 | 9 | .. automethod:: wexpect.wexpect_util.str2bool 10 | .. automethod:: wexpect.wexpect_util.spam 11 | .. automethod:: wexpect.wexpect_util.init_logger 12 | .. automethod:: wexpect.wexpect_util.split_command_line 13 | .. automethod:: wexpect.wexpect_util.join_args 14 | 15 | ExceptionPexpect 16 | ---------------- 17 | 18 | .. autoclass:: ExceptionPexpect 19 | 20 | EOF 21 | --- 22 | 23 | .. autoclass:: EOF 24 | 25 | TIMEOUT 26 | ------- 27 | 28 | .. autoclass:: TIMEOUT 29 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import os 14 | import sys 15 | from pbr.version import VersionInfo 16 | 17 | repo_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) 18 | print(repo_path) 19 | sys.path.insert(0, repo_path) 20 | 21 | # import wexpect 22 | 23 | os.environ['WEXPECT_SPAWN_CLASS'] = 'SpawnPipe' 24 | autodoc_mock_imports = ["pywintypes", "win32process", "win32con", "win32file", "winerror", 25 | "win32pipe", "ctypes", "win32console", "win32gui", "psutil"] 26 | 27 | # from ctypes import windll 28 | 29 | # The master toctree document. 30 | master_doc = 'index' 31 | 32 | # -- Project information ----------------------------------------------------- 33 | 34 | project = 'wexpect' 35 | copyright = '2020, Benedek Racz' 36 | author = 'Benedek Racz' 37 | 38 | # The version info for the project you're documenting, acts as replacement for 39 | # |version| and |release|, also used in various other places throughout the 40 | # built documents. 41 | # 42 | # The short X.Y version. 43 | package_name='wexpect' 44 | info = VersionInfo(package_name) 45 | version = info.version_string() 46 | 47 | # The full version, including alpha/beta/rc tags. 48 | release = version 49 | 50 | 51 | 52 | # -- General configuration --------------------------------------------------- 53 | 54 | # Add any Sphinx extension module names here, as strings. They can be 55 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 56 | # ones. 57 | extensions = [ 58 | 'sphinx.ext.autodoc', 59 | 'sphinx.ext.intersphinx' 60 | ] 61 | 62 | # Add any paths that contain templates here, relative to this directory. 63 | templates_path = ['_templates'] 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | # This pattern also affects html_static_path and html_extra_path. 68 | exclude_patterns = [] 69 | 70 | 71 | # -- Options for HTML output ------------------------------------------------- 72 | 73 | # The theme to use for HTML and HTML Help pages. See the documentation for 74 | # a list of builtin themes. 75 | # 76 | html_theme = 'default' 77 | 78 | # Add any paths that contain custom static files (such as style sheets) here, 79 | # relative to this directory. They are copied after the builtin static files, 80 | # so a file named "default.css" will overwrite the builtin "default.css". 81 | html_static_path = [] 82 | -------------------------------------------------------------------------------- /doc/source/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | `hello_wexpect.py `_ 5 | 6 | This is the simplest example. It starts a windows command interpreter (aka. cmd) lists the current 7 | directory and exits. 8 | 9 | .. code-block:: python 10 | 11 | import wexpect 12 | 13 | # Start cmd as child process 14 | child = wexpect.spawn('cmd.exe') 15 | 16 | # Wait for prompt when cmd becomes ready. 17 | child.expect('>') 18 | 19 | # Prints the cmd's start message 20 | print(child.before, end='') 21 | print(child.after, end='') 22 | 23 | # run list directory command 24 | child.sendline('ls') 25 | 26 | # Waiting for prompt 27 | child.expect('>') 28 | 29 | # Prints content of the directory 30 | print(child.before, end='') 31 | print(child.after, end='') 32 | 33 | # Exit from cmd 34 | child.sendline('exit') 35 | 36 | # Waiting for cmd termination. 37 | child.wait() 38 | 39 | 40 | `terminaton.py `_ 41 | 42 | This script shows three method to terminate your application. `terminate_programmatically()` 43 | shows the recommended way, which kills the child by sending the application specific exit command. 44 | After that waiting for the terminaton is recommended. 45 | `terminate_eof()` shows how to terminate child program by sending EOF character. Some program can be 46 | terminated by sending EOF character. Waiting for the terminaton is recommended in this case too. 47 | `terminate_terminate()` shows how to terminate child program by sending kill signal. Some 48 | application requires sending SIGTERM to kill the child process. `terminate()` call `kill()` 49 | function, which sends SIGTERM to child process. Waiting for the terminaton is not required 50 | explicitly in this case. The wait is included in `terminate()` function. 51 | 52 | .. code-block:: python 53 | 54 | import wexpect 55 | 56 | def terminate_programmatically(): 57 | '''Terminate child program by command. This is the recommended method. Send your application's 58 | exit command to quit the child's process. After that wait for the terminaton. 59 | ''' 60 | print('terminate_programmatically') 61 | 62 | # Start cmd as child process 63 | child = wexpect.spawn('cmd.exe') 64 | 65 | # Wait for prompt when cmd becomes ready. 66 | child.expect('>') 67 | 68 | # Exit from cmd 69 | child.sendline('exit') 70 | 71 | # Waiting for cmd termination. 72 | child.wait() 73 | 74 | def terminate_eof(): 75 | '''Terminate child program by sending EOF character. Some program can be terminated by sending 76 | an EOF character. Waiting for the terminaton is recommended in this case too. 77 | ''' 78 | print('terminate_eof') 79 | 80 | # Start cat as child process 81 | child = wexpect.spawn('cat') 82 | 83 | # Exit by sending EOF 84 | child.sendeof() 85 | 86 | # Waiting for cmd termination. 87 | child.wait() 88 | 89 | 90 | def terminate_terminate(): 91 | '''Terminate child program by sending kill signal. Some application requires sending SIGTERM to kill 92 | the child process. `terminate()` call `kill()` function, which sends SIGTERM to child process. 93 | Waiting for the terminaton is not required explicitly in this case. The wait is included in 94 | `terminate()` function. 95 | ''' 96 | print('terminate_terminate') 97 | 98 | # Start cmd as child process 99 | child = wexpect.spawn('cmd.exe') 100 | 101 | # Wait for prompt when cmd becomes ready. 102 | child.expect('>') 103 | 104 | # Exit from cmd 105 | child.terminate() 106 | 107 | 108 | terminate_programmatically() 109 | terminate_eof() 110 | terminate_terminate() 111 | -------------------------------------------------------------------------------- /doc/source/history.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | Wexpect was a one-file code developed at University of Washington. There were several 5 | `copy `_ and 6 | `reference `_ 7 | to this code with very few (almost none) documentation nor integration. 8 | 9 | This project fixes these limitations, with example codes, tests, and pypi integration. 10 | 11 | Refactor 12 | ^^^^^^^^ 13 | 14 | The original wexpect was a monolith, one-file code, with several structural weaknesses. This leads 15 | me to rewrite the whole code. The first variant of the new structure is delivered with 16 | `v3.2.0 `_. (The default is the old variant 17 | (:code:`legacy_wexpect`) in v3.2.0. :code:`WEXPECT_SPAWN_CLASS` environment variable can choose the 18 | new-structured implementation.) Now :code:`SpawnPipe` is the default spawn class. 19 | 20 | Old vs new 21 | ^^^^^^^^^^ 22 | 23 | But what is the difference between the old and new and what was the problem with the old? 24 | 25 | Generally, wexpect (both old and new) has three processes: 26 | 27 | - *host* is our original python script/program, which want to launch the child. 28 | - *console* is a process which started by the host, and launches the child. (This is a python script) 29 | - *child* is the process which want to be launched. 30 | 31 | The child and the console has a common Windows console, distict from the host. 32 | 33 | The :code:`legacy_wexpect`'s console is a thin script, almost do nothing. It initializes the Windows's 34 | console, and monitors the host and child processes. The magic is done by the host process, which has 35 | the :code:`switchTo()` and :code:`switchBack()` functions, which (de-) attaches the *child-console* 36 | Windows-console. The host manipulates the child's console directly. This direct manipulation is the 37 | main structural weakness. The following task/use-cases are hard/impossible: 38 | 39 | - thread-safe multiprocessing of the host. 40 | - logging (both console and host) 41 | - using in graphical IDE or with pytest 42 | - This variant is highly depends on the pywin32 package. 43 | 44 | The new structure's console is a thick script. The console process do the major console manipulation, 45 | which is controlled by the host via socket (see SpawnSocket) or named-pipe (SpawnPipe). The host 46 | only process the except-loops. 47 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Wexpect version |version| 2 | ========================= 3 | 4 | .. image:: https://ci.appveyor.com/api/projects/status/tbji72d5s0tagrt9?svg=true 5 | :target: https://ci.appveyor.com/project/raczben/wexpect 6 | :align: right 7 | :alt: Build status 8 | 9 | *Wexpect* is a Windows variant of `Pexpect `_ 10 | Wexpect and Pexpect makes Python a better tool for controlling other applications. 11 | 12 | Wexpect is a Python module for spawning child applications; 13 | controlling them; and responding to expected patterns in their output. 14 | Wexpect works like Don Libes' Expect. Wexpect allows your script to 15 | spawn a child application and control it as if a human were typing 16 | commands. 17 | 18 | Wexpect can be used for automating interactive applications such as 19 | ssh, ftp, passwd, telnet, etc. It can be used to a automate setup 20 | scripts for duplicating software package installations on different 21 | servers. It can be used for automated software testing. 22 | Wexpect highly depends on Mark Hammond's `pywin32 `_ 23 | which provides access to many of the Windows APIs from Python. 24 | 25 | Install 26 | ^^^^^^^ 27 | 28 | Wexpect is on PyPI, and can be installed with standard tools:: 29 | 30 | pip install wexpect 31 | 32 | Hello Wexpect 33 | ^^^^^^^^^^^^^ 34 | 35 | To interract with a child process use :code:`spawn` method: 36 | 37 | .. code-block:: python 38 | 39 | import wexpect 40 | child = wexpect.spawn('cmd.exe') 41 | child.expect('>') 42 | child.sendline('ls') 43 | child.expect('>') 44 | print(child.before) 45 | child.sendline('exit') 46 | 47 | 48 | For more information see `examples `_ folder. 49 | 50 | 51 | Contents: 52 | 53 | 54 | .. toctree:: 55 | :maxdepth: 2 56 | 57 | api/index 58 | history 59 | examples 60 | 61 | Wexpect is developed `on Github `_. Please 62 | report `issues `_ there as well. 63 | 64 | Indices and tables 65 | ================== 66 | 67 | * :ref:`genindex` 68 | * :ref:`modindex` 69 | * :ref:`search` 70 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # **wexpect examples** 2 | 3 | There are several example usage of wexpect. Choose one as template of your application. 4 | 5 | ## hello_wexpect 6 | 7 | [hello_wexpect](./hello_wexpect.py) is the simplest example. It starts a windows command interpreter 8 | (aka. cmd) lists the current directory and exits. 9 | 10 | ## python 11 | 12 | [python](./python.py) is a full custom example code. This example script runs [foo](./foo.py) python 13 | program, and communicates with it. For better understanding please run natively foo.py first, which 14 | is a very basic stdio handler script. 15 | 16 | ## cmd_wrapper 17 | 18 | [cmd_wrapper](./cmd_wrapper.py) is a simple wrapper around the cmd windows command interpreter. It 19 | waits for commands executes them in the spawned cmd, and prints the results. 20 | -------------------------------------------------------------------------------- /examples/cmd_wrapper.py: -------------------------------------------------------------------------------- 1 | # A simple example code for wexpect 2 | 3 | from __future__ import print_function 4 | 5 | import sys 6 | import os 7 | import re 8 | 9 | here = os.path.dirname(os.path.abspath(__file__)) 10 | wexpectPath = os.path.dirname(here) 11 | 12 | import wexpect 13 | 14 | # Path of cmd executable: 15 | cmd_exe = 'cmd' 16 | # The prompt should be more sophisticated than just a '>'. 17 | cmdPrompt = re.compile('[A-Z]\:.+>') 18 | 19 | # Start the child process 20 | p = wexpect.spawn(cmd_exe) 21 | 22 | # Wait for prompt 23 | p.expect(cmdPrompt, timeout = 5) 24 | 25 | # print the texts 26 | print(p.before, end='') 27 | print(p.match.group(0), end='') 28 | 29 | 30 | while True: 31 | 32 | # Wait and run a command. 33 | command = input() 34 | p.sendline(command) 35 | 36 | try: 37 | # Wait for prompt 38 | p.expect(cmdPrompt) 39 | 40 | # print the texts 41 | print(p.before, end='') 42 | print(p.match.group(0), end='') 43 | 44 | except wexpect.EOF: 45 | # The program has exited 46 | print('The program has exied... BY!') 47 | break 48 | -------------------------------------------------------------------------------- /examples/foo.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | 7 | # Read an integer from the user: 8 | print('Give a small integer: ', end='') 9 | num = input() 10 | 11 | # Wait the given time 12 | for i in range(int(num)): 13 | print('waiter ' + str(i)) 14 | time.sleep(0.2) 15 | 16 | # Ask the name of the user to say hello. 17 | print('Give your name: ', end='') 18 | name = input() 19 | print('Hello ' + str(name), end='') 20 | -------------------------------------------------------------------------------- /examples/hello_wexpect.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is the simplest example. It starts a windows command interpreter (aka. cmd) lists the current 3 | directory and exits. 4 | ''' 5 | 6 | import wexpect 7 | 8 | # Start cmd as child process 9 | child = wexpect.spawn('cmd.exe') 10 | 11 | # Wait for prompt when cmd becomes ready. 12 | child.expect('>') 13 | 14 | # Prints the cmd's start message 15 | print(child.before, end='') 16 | print(child.after, end='') 17 | 18 | # run dir command to list directory contents 19 | child.sendline('dir') 20 | 21 | # Waiting for prompt 22 | child.expect('>') 23 | 24 | # Prints content of the directory 25 | print(child.before, end='') 26 | print(child.after, end='') 27 | 28 | # Exit from cmd 29 | child.sendline('exit') 30 | 31 | # Waiting for cmd termination. 32 | child.wait() 33 | -------------------------------------------------------------------------------- /examples/python.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This example script runs foo python program, and communicates with it. For better understanding 3 | please run natively foo.py first, which is a very basic stdio handler script. 4 | ''' 5 | 6 | import sys 7 | import wexpect 8 | import os 9 | 10 | here = os.path.dirname(os.path.realpath(__file__)) 11 | 12 | # Path of python executable: 13 | pythonInterp = sys.executable 14 | prompt = ': ' 15 | 16 | # Start the child process 17 | p = wexpect.spawn(pythonInterp, [here + '\\foo.py']) 18 | 19 | # Wait for prompt 20 | p.expect(prompt) 21 | print(p.before) 22 | 23 | # Send the 'small integer' 24 | p.sendline('3') 25 | p.expect(prompt) 26 | print(p.before) 27 | 28 | # print the texts 29 | print(p.before, end='') 30 | print(p.match.group(0), end='') 31 | 32 | # Send the name 33 | p.sendline('Bob') 34 | 35 | # wait for program exit. 36 | p.wait() 37 | 38 | # print the texts 39 | print(p.read(), end='') 40 | -------------------------------------------------------------------------------- /examples/termination.py: -------------------------------------------------------------------------------- 1 | ''' This script shows three method to terminate your application. `terminate_programmatically()` 2 | shows the recommended way, which kills the child by sending the application specific exit command. 3 | After that waiting for the terminaton is recommended. 4 | `terminate_eof()` shows how to terminate child program by sending EOF character. Some program can be 5 | terminated by sending EOF character. Waiting for the terminaton is recommended in this case too. 6 | `terminate_terminate()` shows how to terminate child program by sending kill signal. Some 7 | application requires sending SIGTERM to kill the child process. `terminate()` call `kill()` 8 | function, which sends SIGTERM to child process. Waiting for the terminaton is not required 9 | explicitly in this case. The wait is included in `terminate()` function. 10 | ''' 11 | 12 | import wexpect 13 | 14 | def terminate_programmatically(): 15 | '''Terminate child program by command. This is the recommended method. Send your application's 16 | exit command to quit the child's process. After that wait for the terminaton. 17 | ''' 18 | print('terminate_programmatically') 19 | 20 | # Start cmd as child process 21 | child = wexpect.spawn('cmd.exe') 22 | 23 | # Wait for prompt when cmd becomes ready. 24 | child.expect('>') 25 | 26 | # Exit from cmd 27 | child.sendline('exit') 28 | 29 | # Waiting for cmd termination. 30 | child.wait() 31 | 32 | def terminate_eof(): 33 | '''Terminate child program by sending EOF character. Some program can be terminated by sending 34 | an EOF character. Waiting for the terminaton is recommended in this case too. 35 | ''' 36 | print('terminate_eof') 37 | 38 | # Start cmd as child process 39 | child = wexpect.spawn('cat') 40 | 41 | # Exit from cmd 42 | child.sendeof() 43 | 44 | # Waiting for cmd termination. 45 | child.wait() 46 | 47 | 48 | def terminate_terminate(): 49 | '''Terminate child program by sending kill signal. Some application requires sending SIGTERM to kill 50 | the child process. `terminate()` call `kill()` function, which sends SIGTERM to child process. 51 | Waiting for the terminaton is not required explicitly in this case. The wait is included in 52 | `terminate()` function. 53 | ''' 54 | print('terminate_terminate') 55 | 56 | # Start cmd as child process 57 | child = wexpect.spawn('cmd.exe') 58 | 59 | # Wait for prompt when cmd becomes ready. 60 | child.expect('>') 61 | 62 | # Exit from cmd 63 | child.terminate() 64 | 65 | 66 | terminate_programmatically() 67 | terminate_eof() 68 | terminate_terminate() 69 | -------------------------------------------------------------------------------- /issues/README.md: -------------------------------------------------------------------------------- 1 | issues folder contains scripts to help to reproduce a given issue 2 | -------------------------------------------------------------------------------- /issues/bla.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | # Wait the given time 4 | for i in range(2): 5 | print('apple\tbanana\tmelone\tcherry') 6 | print('pepper tomato\tcorn cabbage') 7 | print('apple banana\tmelone\tcherry2') 8 | print('pepper tomato\tcorn cabbage2') 9 | print('prompt> ') 10 | time.sleep(0.5) 11 | 12 | -------------------------------------------------------------------------------- /issues/bla2.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | 7 | 8 | print('Welcome!') 9 | 10 | while True: 11 | print('>', end='') 12 | cmd = input() 13 | 14 | # Wait the given time 15 | if cmd == 'ls': 16 | print('apple\tbanana\tmelone\tcherry') 17 | print('pepper tomato\tcorn cabbage') 18 | print('apple banana\tmelone\tcherry2') 19 | print('pepper tomato\tcorn cabbage2') 20 | continue 21 | if cmd == 'exit': 22 | break 23 | else: 24 | print('unknown command') -------------------------------------------------------------------------------- /issues/i09_cmd_error.py: -------------------------------------------------------------------------------- 1 | 2 | import wexpect 3 | import time 4 | 5 | print(wexpect.__version__) 6 | 7 | 8 | def testPath(): 9 | # Path of cmd executable: 10 | cmdPath = 'cmd' 11 | cmdPrompt = '>' 12 | referenceOut = None 13 | 14 | while True: 15 | # Start the child process 16 | p = wexpect.spawn(cmdPath) 17 | 18 | # Wait for prompt 19 | p.expect(cmdPrompt) 20 | 21 | # Send a command 22 | p.sendline('ls') 23 | # time.sleep(0.6) 24 | p.expect(cmdPrompt) 25 | 26 | 27 | print(cmdPath + " >>" + p.before + '<<') 28 | if referenceOut: 29 | if referenceOut != p.before: 30 | p.interact() 31 | time.sleep(5) 32 | raise Exception() 33 | else: 34 | referenceOut = p.before 35 | 36 | testPath() 37 | 38 | -------------------------------------------------------------------------------- /issues/i09_confused_string.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import time 3 | 4 | print(wexpect.__version__) 5 | 6 | 7 | def testPath(): 8 | # Path of cmd executable: 9 | cmdPath = 'cmd' 10 | cmdPrompt = '>' 11 | referenceOut = None 12 | 13 | while True: 14 | # Start the child process 15 | p = wexpect.spawn(cmdPath) 16 | 17 | # Wait for prompt 18 | p.expect(cmdPrompt) 19 | 20 | # Send a command 21 | p.sendline('ls') 22 | # time.sleep(0.6) <= this sleep solve... 23 | p.expect(cmdPrompt) 24 | 25 | print(cmdPath + " >>" + p.before + '<<') 26 | if referenceOut: 27 | if referenceOut != p.before: 28 | p.interact() 29 | time.sleep(5) 30 | raise Exception() 31 | else: 32 | referenceOut = p.before 33 | 34 | testPath() 35 | -------------------------------------------------------------------------------- /issues/i09_py_sim_no_error.py: -------------------------------------------------------------------------------- 1 | 2 | import wexpect 3 | import time 4 | 5 | print(wexpect.__version__) 6 | 7 | 8 | def testPath(): 9 | # Path of cmd executable: 10 | cmdPath = 'python bugfix\\bla.py' 11 | cmdPrompt = '> ' 12 | referenceOut = None 13 | 14 | while True: 15 | # Start the child process 16 | p = wexpect.spawn(cmdPath) 17 | 18 | # Wait for prompt 19 | p.expect(cmdPrompt) 20 | # Wait for prompt 21 | p.expect(cmdPrompt) 22 | 23 | print(cmdPath + " >>" + p.before + '<<') 24 | if referenceOut: 25 | if referenceOut != p.before: 26 | p.interact() 27 | time.sleep(5) 28 | raise Exception() 29 | else: 30 | referenceOut = p.before 31 | 32 | testPath() 33 | 34 | -------------------------------------------------------------------------------- /issues/i09_py_sim_no_error2.py: -------------------------------------------------------------------------------- 1 | 2 | import wexpect 3 | import time 4 | 5 | print(wexpect.__version__) 6 | 7 | 8 | def testPath(): 9 | # Path of cmd executable: 10 | cmdPath = 'python bugfix\\bla2.py' 11 | cmdPrompt = '>' 12 | referenceOut = None 13 | 14 | while True: 15 | # Start the child process 16 | p = wexpect.spawn(cmdPath) 17 | 18 | # Wait for prompt 19 | p.expect(cmdPrompt) 20 | 21 | # Send a command 22 | p.sendline('ls') 23 | # time.sleep(0.6) 24 | p.expect(cmdPrompt) 25 | 26 | 27 | print(cmdPath + " >>" + p.before + '<<') 28 | if referenceOut: 29 | if referenceOut != p.before: 30 | p.interact() 31 | time.sleep(5) 32 | raise Exception() 33 | else: 34 | referenceOut = p.before 35 | 36 | testPath() 37 | 38 | -------------------------------------------------------------------------------- /issues/i10_parent.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import time 3 | import sys 4 | import os 5 | 6 | here = os.path.dirname(os.path.abspath(__file__)) 7 | sys.path.insert(0, here) 8 | 9 | from long_printer import puskas_wiki 10 | 11 | print(wexpect.__version__) 12 | 13 | 14 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 15 | python_executable = '"' + sys.executable + '" ' 16 | child_script = here + '\\long_printer.py' 17 | 18 | 19 | def main(): 20 | longPrinter = python_executable + ' ' + child_script 21 | prompt = 'puskas> ' 22 | 23 | # Start the child process 24 | p = wexpect.spawn(longPrinter) 25 | # Wait for prompt 26 | p.expect(prompt) 27 | 28 | try: 29 | for i in range(10): 30 | print('.', end='') 31 | p.sendline('0') 32 | p.expect(prompt) 33 | if p.before.splitlines()[1] != puskas_wiki[0]: 34 | print(p.before.splitlines()[1]) 35 | raise Exception() 36 | 37 | p.sendline('all') 38 | p.expect(prompt) 39 | for a,b in zip(p.before.splitlines()[1:], puskas_wiki): 40 | if a!=b: 41 | print(a) 42 | print(b) 43 | raise Exception() 44 | 45 | for j, paragraph in enumerate(puskas_wiki): 46 | p.sendline(str(j)) 47 | p.expect(prompt) 48 | if p.before.splitlines()[1] != paragraph: 49 | print(p.before.splitlines()[1]) 50 | print(i) 51 | print(j) 52 | print(paragraph) 53 | raise Exception() 54 | except: 55 | p.interact() 56 | time.sleep(5) 57 | else: 58 | print('') 59 | print('[PASS]') 60 | 61 | 62 | main() 63 | 64 | -------------------------------------------------------------------------------- /issues/i11_greek_parent.py: -------------------------------------------------------------------------------- 1 | 2 | import wexpect 3 | import time 4 | import sys 5 | import os 6 | 7 | here = os.path.dirname(os.path.abspath(__file__)) 8 | sys.path.insert(0, here) 9 | 10 | import i11_greek_printer 11 | 12 | print(wexpect.__version__) 13 | 14 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 15 | python_executable = '"' + sys.executable + '" ' 16 | child_script = here + '\\i11_greek_printer.py' 17 | 18 | 19 | def main(): 20 | unicodePrinter = python_executable + ' ' + child_script 21 | prompt = 'give the name of a greek letter> ' 22 | 23 | # Start the child process 24 | print('starting child: ' + unicodePrinter) 25 | p = wexpect.spawn(unicodePrinter) 26 | print('waiting for prompt') 27 | 28 | # Wait for prompt 29 | p.expect(prompt) 30 | print('Child prompt arrived, lets start commands!') 31 | 32 | # Send commands 33 | for letterName in i11_greek_printer.greekLetters.keys(): 34 | p.sendline(letterName) 35 | p.expect(prompt) 36 | print(p.before.splitlines()[1]) 37 | if i11_greek_printer.greekLetters[letterName] != p.before.splitlines()[1]: 38 | p.interact() 39 | time.sleep(5) 40 | raise Exception() 41 | 42 | 43 | main() 44 | 45 | -------------------------------------------------------------------------------- /issues/i11_greek_printer.py: -------------------------------------------------------------------------------- 1 | greekLetters = { 2 | 'ALPHA': '\u03B1', 3 | 'BETA': '\u03B2', 4 | 'GAMMA': '\u03B3', 5 | 'DELTA': '\u03B4', 6 | 'EPSILON': '\u03B5', 7 | 'ZETA': '\u03B6', 8 | 'ETA': '\u03B7', 9 | 'THETA': '\u03B8', 10 | 'IOTA': '\u03B9', 11 | 'KAPPA': '\u03BA', 12 | 'LAMDA': '\u03BB', 13 | 'MU': '\u03BC', 14 | 'NU': '\u03BD', 15 | 'XI': '\u03BE', 16 | 'OMICRON': '\u03BF', 17 | 'PI': '\u03C0', 18 | 'RHO': '\u03C1', 19 | 'FINAL SIGMA': '\u03C2', 20 | 'SIGMA': '\u03C3', 21 | 'TAU': '\u03C4', 22 | 'UPSILON': '\u03C5', 23 | 'PHI': '\u03C6', 24 | 'CHI': '\u03C7', 25 | 'PSI': '\u03C8', 26 | 'OMEGA': '\u03C9' 27 | } 28 | 29 | def main(): 30 | while True: 31 | letter = input('give the name of a greek letter> ') 32 | if letter.lower() == 'exit': 33 | print('Bye') 34 | break 35 | if letter.lower() == 'all': 36 | print(greekLetters) 37 | continue 38 | try: 39 | print(greekLetters[letter.upper()]) 40 | except: 41 | print('ERROR!!! Uunknkown letter') 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /issues/i11_unicode_parent.py: -------------------------------------------------------------------------------- 1 | 2 | import wexpect 3 | import time 4 | import sys 5 | import os 6 | 7 | here = os.path.dirname(os.path.abspath(__file__)) 8 | sys.path.insert(0, here) 9 | 10 | import i11_unicode_printer 11 | 12 | print(wexpect.__version__) 13 | 14 | encodings = ['cp1250', 'utf-8'] 15 | 16 | 17 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 18 | python_executable = '"' + sys.executable + '" ' 19 | child_script = here + '\\i11_unicode_printer.py' 20 | 21 | 22 | def main(): 23 | unicodePrinter = python_executable + ' ' + child_script 24 | prompt = '> ' 25 | 26 | for ec in encodings: 27 | # Start the child process 28 | p = wexpect.spawn(unicodePrinter) 29 | 30 | # Wait for prompt 31 | p.expect(prompt) 32 | print('Child prompt arrived, lets set encoding!') 33 | p.sendline(ec) 34 | p.expect(prompt) 35 | 36 | # Send commands 37 | for cc in range(34, 500): 38 | p.sendline(str(cc)) 39 | p.expect(prompt) 40 | print(p.before.splitlines()[1]) 41 | if chr(int(cc)).encode("utf-8").decode(ec) != p.before.splitlines()[1]: 42 | p.interact() 43 | time.sleep(5) 44 | raise Exception() 45 | 46 | 47 | main() 48 | 49 | -------------------------------------------------------------------------------- /issues/i11_unicode_printer.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def main(): 4 | encoding = "utf-8" 5 | encoding = input('give the encoding> ') 6 | while True: 7 | code = input('give a number> ') 8 | if code.lower() == 'exit': 9 | print('Bye') 10 | break 11 | try: 12 | print(chr(int(code)).encode("utf-8").decode(encoding) ) 13 | # code_int = int(code) 14 | # print(greekLetters[letter.upper()]) 15 | except: 16 | print('ERROR!!!') 17 | raise 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /issues/i18_setecho_error.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import time 3 | import os 4 | 5 | print(wexpect.__version__) 6 | 7 | def test_setecho(): 8 | # Path of cmd executable: 9 | cmdPath = 'cmd' 10 | cmdPrompt = '>' 11 | referenceOut = None 12 | 13 | # Start the child process 14 | p = wexpect.spawn(cmdPath) 15 | p.expect(cmdPrompt) 16 | 17 | p.setecho(0) # SETECHO 18 | 19 | p.interact() 20 | for c in 'echo Hello': 21 | p.send(c) 22 | time.sleep(0.2) 23 | p.send(os.linesep) 24 | 25 | time.sleep(2) 26 | p.stop_interact() 27 | p.sendline('exit') 28 | 29 | 30 | # Start the child process 31 | p = wexpect.spawn(cmdPath) 32 | p.expect(cmdPrompt) 33 | 34 | p.setecho(1) # SETECHO 35 | 36 | p.interact() 37 | for c in 'echo Hello': 38 | p.send(c) 39 | time.sleep(0.2) 40 | p.send(os.linesep) 41 | 42 | time.sleep(2) 43 | p.stop_interact() 44 | p.sendline('exit') 45 | 46 | 47 | test_setecho() 48 | -------------------------------------------------------------------------------- /issues/long_printer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | 7 | puskas_wiki = ['''Ferenc Puskas was a Hungarian footballer and manager, widely regarded as one of \ 8 | the greatest players of all time. He is the son of former footballer Ferenc Puskas Senior. A \ 9 | prolific forward, he scored 84 goals in 85 international matches for Hungary, played 4 \ 10 | international matches for Spain and scored 514 goals in 529 matches in the Hungarian and Spanish \ 11 | leagues. He became an Olympic champion in 1952 and led his nation to the final of the 1954 World \ 12 | Cup where he was named the tournament's best player. He won three European Cups (1959, 1960, 1966),\ 13 | 10 national championships (5 Hungarian and 5 Spanish Primera Division) and 8 top individual \ 14 | scoring honors. In 1995, he was recognized as the top scorer of the 20th century by the IFFHS.''', 15 | '''Puskas started his career in Hungary playing for Kispest and Budapest Honved. He was the top scorer\ 16 | in the Hungarian League on four occasions, and in 1948, he was the top goal scorer in Europe. \ 17 | During the 1950s, he was both a prominent member and captain of the Hungarian national team, known\ 18 | as the Mighty Magyars. In 1958, two years after the Hungarian Revolution, he emigrated to Spain \ 19 | where he played for Real Madrid. While playing with Real Madrid, Puskas won four Pichichis and \ 20 | scored seven goals in two European Champions Cup finals.''', 21 | '''After retiring as a player, he became a coach. The highlight of his coaching career came in 1971 \ 22 | when he guided Panathinaikos to the European Cup final, where they lost 2-0 to AFC Ajax. In 1993, \ 23 | he returned to Hungary and took temporary charge of the Hungarian national team. In 1998, he \ 24 | became one of the first ever FIFA/SOS Charity ambassadors. In 2002, the Nepstadion in Budapest \ 25 | was renamed the Puskas Ferenc Stadion in his honor. He was also declared the best Hungarian \ 26 | player of the last 50 years by the Hungarian Football Federation in the UEFA Jubilee Awards in \ 27 | November 2003. In October 2009, FIFA announced the introduction of the FIFA Puskas Award, \ 28 | awarded to the player who has scored the "most beautiful goal" over the past year. He was also \ 29 | listed in Pele's FIFA 100.''', 30 | '''Ferenc Purczeld was born on 2 April 1927 to a German (Danube Swabian) family in Budapest and \ 31 | brought up in Kispest, then a suburb, today part of the city. His mother, Margit Biro \ 32 | (1904-1976), was a seamstress. He began his career as a junior with Kispest AC,[10] where his \ 33 | father, who had previously played for the club, was a coach. He had grandchildren, who were the \ 34 | children of his brothers son[clarification needed]; the two sons of his brother are Zoltan and \ 35 | Istvan, the first one have 3 children; Ilonka, Camila and Andres, and the second one have two.''', 36 | '''He changed his name to Puskas. He initially used the pseudonym "Miklos Kovacs" to help \ 37 | circumvent the minimum age rules[12] before officially signing at the age of 12. Among his early \ 38 | teammates was his childhood friend and future international teammate Jozsef Bozsik. He made his \ 39 | first senior appearance for Kispest in November 1943 in a match against Nagyvaradi AC.[13] It was \ 40 | here where he got the nickname "Ocsi" or "Buddy".[14]''', 41 | '''Kispest was taken over by the Hungarian Ministry of Defence in 1949, becoming the Hungarian Army \ 42 | team and changing its name to Budapest Honved. As a result, football players were given military \ 43 | ranks. Puskas eventually became a major (Hungarian: Ornagy), which led to the nickname "The \ 44 | Galloping Major".[15] As the army club, Honved used conscription to acquire the best Hungarian \ 45 | players, leading to the recruitment of Zoltan Czibor and Sandor Kocsis.[16] During his career at \ 46 | Budapest Honved, Puskas helped the club win five Hungarian League titles. He also finished as top \ 47 | goal scorer in the league in 1947-48, 1949-50, 1950 and 1953, scoring 50, 31, 25 and 27 goals, \ 48 | respectively. In 1948, he was the top goal scorer in Europe.[17]''' ] 49 | 50 | def main(): 51 | print('Welcome!') 52 | 53 | while True: 54 | print('puskas> ', end='') 55 | num = input() 56 | 57 | if num == 'exit': 58 | break 59 | if num == 'all': 60 | print('\r\n'.join(puskas_wiki)) 61 | continue 62 | try: 63 | if int(num) in range(len(puskas_wiki)): 64 | print(puskas_wiki[int(num)]) 65 | continue 66 | except: 67 | pass 68 | print('unknown command') 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pywin32>=220 2 | psutil>=5.0.0 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = wexpect 3 | author = Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, Benedek Racz 4 | author-email = betontalpfa@gmail.com 5 | summary = Windows alternative of pexpect 6 | description-file = README.md 7 | long-description-content-type = text/markdown 8 | requires-python = >=3.4 9 | project_urls = 10 | Source Code = https://github.com/raczben/wexpect 11 | license = MIT 12 | classifier = 13 | Development Status :: 4 - Beta 14 | Environment :: Console 15 | Intended Audience :: Developers 16 | Intended Audience :: Information Technology 17 | Operating System :: Microsoft :: Windows 18 | Programming Language :: Python :: 3.3 19 | Programming Language :: Python :: 3.4 20 | Programming Language :: Python :: 3.5 21 | Programming Language :: Python :: 3.6 22 | Programming Language :: Python :: 3.7 23 | keywords = 24 | scripting, automation, expect, pexpect, wexpect 25 | 26 | [options.extras_require] 27 | test = 28 | coverage 29 | tox 30 | setuptools>=38.0 31 | codecov 32 | twine 33 | pyinstaller 34 | 35 | [flake8] 36 | max-line-length = 100 37 | ignore = 38 | # E402: Module level import not at top of the file 39 | E402 40 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | setup_requires=['pbr'], 7 | pbr=True, 8 | 9 | # packages=[''], 10 | py_modules=['wexpect'], 11 | ) -------------------------------------------------------------------------------- /tests/PexpectTestCase.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import contextlib 4 | import unittest 5 | import signal 6 | import sys 7 | import os 8 | 9 | 10 | class PexpectTestCase(unittest.TestCase): 11 | def setUp(self): 12 | self.PYTHONBIN = sys.executable 13 | self.original_path = os.getcwd() 14 | tests_dir = os.path.dirname(__file__) 15 | self.project_dir = project_dir = os.path.dirname(tests_dir) 16 | 17 | # all tests are executed in this folder; there are many auxiliary 18 | # programs in this folder executed by spawn(). 19 | os.chdir(tests_dir) 20 | 21 | # If the pexpect raises an exception after fork(), but before 22 | # exec(), our test runner *also* forks. We prevent this by 23 | # storing our pid and asserting equality on tearDown. 24 | self.pid = os.getpid() 25 | 26 | coverage_rc = os.path.join(project_dir, '.coveragerc') 27 | os.environ['COVERAGE_PROCESS_START'] = coverage_rc 28 | os.environ['COVERAGE_FILE'] = os.path.join(project_dir, '.coverage') 29 | print('\n', self.id(), end=' ') 30 | sys.stdout.flush() 31 | 32 | # some build agents will ignore SIGHUP and SIGINT, which python 33 | # inherits. This causes some of the tests related to terminate() 34 | # to fail. We set them to the default handlers that they should 35 | # be, and restore them back to their SIG_IGN value on tearDown. 36 | # 37 | # I'm not entirely convinced they need to be restored, only our 38 | # test runner is affected. 39 | self.restore_ignored_signals = [ 40 | value for value in (signal.SIGINT,) 41 | if signal.getsignal(value) == signal.SIG_IGN] 42 | if signal.SIGINT in self.restore_ignored_signals: 43 | # SIGINT should be set to signal.default_int_handler 44 | signal.signal(signal.SIGINT, signal.default_int_handler) 45 | unittest.TestCase.setUp(self) 46 | 47 | def tearDown(self): 48 | # restore original working folder 49 | os.chdir(self.original_path) 50 | 51 | if self.pid != os.getpid(): 52 | # The build server pattern-matches phrase 'Test runner has forked!' 53 | print("Test runner has forked! This means a child process raised " 54 | "an exception before exec() in a test case, the error is " 55 | "more than likely found above this line in stderr.", 56 | file=sys.stderr) 57 | exit(1) 58 | 59 | # restore signal handlers 60 | for signal_value in self.restore_ignored_signals: 61 | signal.signal(signal_value, signal.SIG_IGN) 62 | 63 | if sys.version_info < (2, 7): 64 | # We want to use these methods, which are new/improved in 2.7, but 65 | # we are still supporting 2.6 for the moment. This section can be 66 | # removed when we drop Python 2.6 support. 67 | @contextlib.contextmanager 68 | def assertRaises(self, excClass): 69 | try: 70 | yield 71 | except Exception as e: 72 | assert isinstance(e, excClass) 73 | else: 74 | raise AssertionError("%s was not raised" % excClass) 75 | 76 | @contextlib.contextmanager 77 | def assertRaisesRegexp(self, excClass, pattern): 78 | import re 79 | try: 80 | yield 81 | except Exception as e: 82 | assert isinstance(e, excClass) 83 | assert re.match(pattern, str(e)) 84 | else: 85 | raise AssertionError("%s was not raised" % excClass) 86 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | # The mere presence of this file makes the dir a package. 3 | pass 4 | -------------------------------------------------------------------------------- /tests/echo_w_prompt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function 3 | 4 | try: 5 | raw_input 6 | except NameError: 7 | raw_input = input 8 | 9 | while True: 10 | try: 11 | a = raw_input('') 12 | except EOFError: 13 | print('') 14 | break 15 | print('', a, sep='') 16 | -------------------------------------------------------------------------------- /tests/exit1.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | print("Hello") 4 | sys.stdout.flush() 5 | os._exit(1) 6 | -------------------------------------------------------------------------------- /tests/foo.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | 7 | # Read an integer from the user: 8 | print('Give a small integer: ', end='') 9 | num = input() 10 | 11 | # Wait the given time 12 | for i in range(int(num)): 13 | print('waiter ' + str(i)) 14 | time.sleep(0.2) 15 | 16 | # Ask the name of the user to say hello. 17 | print('Give your name: ', end='') 18 | name = input() 19 | print('Hello ' + str(name), end='') 20 | -------------------------------------------------------------------------------- /tests/lines_printer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | 7 | # Read an integer from the user: 8 | print('Give a small integer: ', end='') 9 | num = input() 10 | 11 | # Wait the given time 12 | for i in range(int(num)): 13 | print('waiter ' + str(i)) 14 | time.sleep(0.2) 15 | print('Bye!') -------------------------------------------------------------------------------- /tests/list100.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | print(list(range(100))) 3 | -------------------------------------------------------------------------------- /tests/long_printer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | 7 | puskas_wiki = ['''Ferenc Puskas was a Hungarian footballer and manager, widely regarded as one of \ 8 | the greatest players of all time. He is the son of former footballer Ferenc Puskas Senior. A \ 9 | prolific forward, he scored 84 goals in 85 international matches for Hungary, played 4 \ 10 | international matches for Spain and scored 514 goals in 529 matches in the Hungarian and Spanish \ 11 | leagues. He became an Olympic champion in 1952 and led his nation to the final of the 1954 World \ 12 | Cup where he was named the tournament's best player. He won three European Cups (1959, 1960, 1966),\ 13 | 10 national championships (5 Hungarian and 5 Spanish Primera Division) and 8 top individual \ 14 | scoring honors. In 1995, he was recognized as the top scorer of the 20th century by the IFFHS.''', 15 | '''Puskas started his career in Hungary playing for Kispest and Budapest Honved. He was the top scorer\ 16 | in the Hungarian League on four occasions, and in 1948, he was the top goal scorer in Europe. \ 17 | During the 1950s, he was both a prominent member and captain of the Hungarian national team, known\ 18 | as the Mighty Magyars. In 1958, two years after the Hungarian Revolution, he emigrated to Spain \ 19 | where he played for Real Madrid. While playing with Real Madrid, Puskas won four Pichichis and \ 20 | scored seven goals in two European Champions Cup finals.''', 21 | '''After retiring as a player, he became a coach. The highlight of his coaching career came in 1971 \ 22 | when he guided Panathinaikos to the European Cup final, where they lost 2-0 to AFC Ajax. In 1993, \ 23 | he returned to Hungary and took temporary charge of the Hungarian national team. In 1998, he \ 24 | became one of the first ever FIFA/SOS Charity ambassadors. In 2002, the Nepstadion in Budapest \ 25 | was renamed the Puskas Ferenc Stadion in his honor. He was also declared the best Hungarian \ 26 | player of the last 50 years by the Hungarian Football Federation in the UEFA Jubilee Awards in \ 27 | November 2003. In October 2009, FIFA announced the introduction of the FIFA Puskas Award, \ 28 | awarded to the player who has scored the "most beautiful goal" over the past year. He was also \ 29 | listed in Pele's FIFA 100.''', 30 | '''Ferenc Purczeld was born on 2 April 1927 to a German (Danube Swabian) family in Budapest and \ 31 | brought up in Kispest, then a suburb, today part of the city. His mother, Margit Biro \ 32 | (1904-1976), was a seamstress. He began his career as a junior with Kispest AC,[10] where his \ 33 | father, who had previously played for the club, was a coach. He had grandchildren, who were the \ 34 | children of his brothers son[clarification needed]; the two sons of his brother are Zoltan and \ 35 | Istvan, the first one have 3 children; Ilonka, Camila and Andres, and the second one have two.''', 36 | '''He changed his name to Puskas. He initially used the pseudonym "Miklos Kovacs" to help \ 37 | circumvent the minimum age rules[12] before officially signing at the age of 12. Among his early \ 38 | teammates was his childhood friend and future international teammate Jozsef Bozsik. He made his \ 39 | first senior appearance for Kispest in November 1943 in a match against Nagyvaradi AC.[13] It was \ 40 | here where he got the nickname "Ocsi" or "Buddy".[14]''', 41 | '''Kispest was taken over by the Hungarian Ministry of Defence in 1949, becoming the Hungarian Army \ 42 | team and changing its name to Budapest Honved. As a result, football players were given military \ 43 | ranks. Puskas eventually became a major (Hungarian: Ornagy), which led to the nickname "The \ 44 | Galloping Major".[15] As the army club, Honved used conscription to acquire the best Hungarian \ 45 | players, leading to the recruitment of Zoltan Czibor and Sandor Kocsis.[16] During his career at \ 46 | Budapest Honved, Puskas helped the club win five Hungarian League titles. He also finished as top \ 47 | goal scorer in the league in 1947-48, 1949-50, 1950 and 1953, scoring 50, 31, 25 and 27 goals, \ 48 | respectively. In 1948, he was the top goal scorer in Europe.[17]''' ] 49 | 50 | def main(): 51 | print('Welcome!') 52 | 53 | while True: 54 | print('puskas> ', end='') 55 | num = input() 56 | 57 | if num == 'exit': 58 | break 59 | if num == 'all': 60 | print('\r\n'.join(puskas_wiki)) 61 | continue 62 | try: 63 | if int(num) in range(len(puskas_wiki)): 64 | print(puskas_wiki[int(num)]) 65 | continue 66 | except: 67 | pass 68 | print('unknown command') 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /tests/needs_kill.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This script can only be killed by SIGKILL.""" 3 | import signal, time 4 | 5 | # Ignore interrupt, hangup and continue signals - only SIGKILL will work 6 | signal.signal(signal.SIGINT, signal.SIG_IGN) 7 | 8 | print('READY') 9 | while True: 10 | time.sleep(10) 11 | -------------------------------------------------------------------------------- /tests/parametric_printer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is is a very basic stdio handler script. This is used by python.py example. 3 | ''' 4 | 5 | import time 6 | import sys 7 | 8 | # Read an integer from the user: 9 | 10 | while True: 11 | print('Format:character,character_count,line_count,speed_ms> ', end='') 12 | command = input() 13 | (character,character_count,line_count,speed_ms) = command.split(',') 14 | character_count = int(character_count) 15 | speed_ms = int(speed_ms) 16 | line_count = int(line_count) 17 | 18 | if line_count<1: 19 | sys.exit(0) 20 | for _ in range(line_count): 21 | if speed_ms<0: 22 | print(character*character_count) 23 | sys.stdout.flush() 24 | else: 25 | for i in range(character_count): 26 | if i == character_count-1: 27 | print(character) 28 | sys.stdout.flush() 29 | else: 30 | print(character, end='') 31 | sys.stdout.flush() 32 | time.sleep(speed_ms/1000) 33 | -------------------------------------------------------------------------------- /tests/pyinstaller_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import re 4 | import os 5 | import time 6 | 7 | import wexpect 8 | 9 | # the program cat(1) may display ^D\x08\x08 when \x04 (EOF, Ctrl-D) is sent 10 | _CAT_EOF = '^D\x08\x08' 11 | 12 | def _u(s): 13 | return s 14 | 15 | class TestCaseMisc(unittest.TestCase): 16 | 17 | def test_isatty(self): 18 | " Test isatty() is True after spawning process on most platforms. " 19 | child = wexpect.spawn('cat') 20 | if not child.isatty() and sys.platform.lower().startswith('sunos'): 21 | if hasattr(unittest, 'SkipTest'): 22 | raise unittest.SkipTest("Not supported on this platform.") 23 | return 'skip' 24 | assert child.isatty() 25 | 26 | def test_read(self): 27 | " Test spawn.read by calls of various size. " 28 | child = wexpect.spawn('cat') 29 | child.sendline("abc") 30 | child.sendeof() 31 | child.readline() 32 | self.assertEqual(child.read(0), '') 33 | self.assertEqual(child.read(1), 'a') 34 | self.assertEqual(child.read(1), 'b') 35 | self.assertEqual(child.read(1), 'c') 36 | self.assertEqual(child.read(2), '\r\n') 37 | 38 | def test_readline_bin_echo(self): 39 | " Test spawn('echo'). " 40 | # given, 41 | child = wexpect.spawn('echo', ['alpha', 'beta']) 42 | 43 | # exercise, 44 | self.assertEqual(child.readline(), 'alpha beta\r\n') 45 | 46 | def test_readline(self): 47 | " Test spawn.readline(). " 48 | # when argument 0 is sent, nothing is returned. 49 | # Otherwise the argument value is meaningless. 50 | child = wexpect.spawn('cat') 51 | child.sendline("alpha") 52 | child.sendline("beta") 53 | child.sendline("gamma") 54 | child.sendline("delta") 55 | child.sendeof() 56 | self.assertEqual(child.readline(0), '') 57 | child.readline().rstrip() 58 | self.assertEqual(child.readline().rstrip(), 'alpha') 59 | child.readline().rstrip() 60 | self.assertEqual(child.readline(1).rstrip(), 'beta') 61 | child.readline().rstrip() 62 | self.assertEqual(child.readline(2).rstrip(), 'gamma') 63 | child.readline().rstrip() 64 | self.assertEqual(child.readline().rstrip(), 'delta') 65 | child.expect(wexpect.EOF) 66 | if type(child).__name__ in ['SpawnPipe', 'SpawnSocket']: 67 | time.sleep(child.delayafterterminate) 68 | assert not child.isalive(trust_console=False) 69 | else: 70 | assert not child.isalive() 71 | self.assertEqual(child.exitstatus, 0) 72 | 73 | def test_iter(self): 74 | " iterating over lines of spawn.__iter__(). " 75 | child = wexpect.spawn('echo "abc\r\n123"') 76 | # Don't use ''.join() because we want to test __iter__(). 77 | page = '' 78 | for line in child: 79 | page += line 80 | page = page.replace(_CAT_EOF, '') 81 | self.assertEqual(page, 'abc\r\n123\r\n') 82 | 83 | def test_readlines(self): 84 | " reading all lines of spawn.readlines(). " 85 | child = wexpect.spawn('cat', echo=False) 86 | child.sendline("abc") 87 | child.sendline("123") 88 | child.sendeof() 89 | page = ''.join(child.readlines()).replace(_CAT_EOF, '') 90 | self.assertEqual(page, '\r\nabc\r\n\r\n123\r\n') 91 | child.expect(wexpect.EOF) 92 | if type(child).__name__ in ['SpawnPipe', 'SpawnSocket']: 93 | time.sleep(child.delayafterterminate) 94 | assert not child.isalive(trust_console=False) 95 | else: 96 | assert not child.isalive() 97 | self.assertEqual(child.exitstatus, 0) 98 | 99 | def test_write(self): 100 | " write a character and return it in return. " 101 | child = wexpect.spawn('cat') 102 | child.write('a') 103 | child.write('\r') 104 | child.readline() 105 | self.assertEqual(child.readline(), 'a\r\n') 106 | 107 | def test_writelines(self): 108 | " spawn.writelines() " 109 | child = wexpect.spawn('cat') 110 | # notice that much like file.writelines, we do not delimit by newline 111 | # -- it is equivalent to calling write(''.join([args,])) 112 | child.writelines(['abc', '123', 'xyz', '\r']) 113 | child.sendeof() 114 | child.readline() 115 | line = child.readline() 116 | self.assertEqual(line, 'abc123xyz\r\n') 117 | 118 | def test_eof(self): 119 | " call to expect() after EOF is received raises wexpect.EOF " 120 | child = wexpect.spawn('cat') 121 | child.sendeof() 122 | with self.assertRaises(wexpect.EOF): 123 | child.expect('the unexpected') 124 | 125 | def test_terminate(self): 126 | " test force terminate always succeeds (SIGKILL). " 127 | child = wexpect.spawn('cat') 128 | child.terminate(force=1) 129 | assert child.terminated 130 | 131 | def test_bad_arguments_suggest_fdpsawn(self): 132 | " assert custom exception for spawn(int). " 133 | expect_errmsg = "maybe you want to use fdpexpect.fdspawn" 134 | with self.assertRaisesRegex(wexpect.ExceptionPexpect, 135 | ".*" + expect_errmsg): 136 | wexpect.spawn(1) 137 | 138 | def test_bad_arguments_second_arg_is_list(self): 139 | " Second argument to spawn, if used, must be only a list." 140 | with self.assertRaises(TypeError): 141 | wexpect.spawn('ls', '-la') 142 | 143 | with self.assertRaises(TypeError): 144 | # not even a tuple, 145 | wexpect.spawn('ls', ('-la',)) 146 | 147 | def test_read_after_close_raises_value_error(self): 148 | " Calling read_nonblocking after close raises ValueError. " 149 | # as read_nonblocking underlies all other calls to read, 150 | # ValueError should be thrown for all forms of read. 151 | with self.assertRaises(ValueError): 152 | p = wexpect.spawn('cat') 153 | p.close() 154 | p.read_nonblocking() 155 | 156 | with self.assertRaises(ValueError): 157 | p = wexpect.spawn('cat') 158 | p.close() 159 | p.read() 160 | 161 | with self.assertRaises(ValueError): 162 | p = wexpect.spawn('cat') 163 | p.close() 164 | p.readline() 165 | 166 | with self.assertRaises(ValueError): 167 | p = wexpect.spawn('cat') 168 | p.close() 169 | p.readlines() 170 | 171 | def test_isalive(self): 172 | " check isalive() before and after EOF. (True, False) " 173 | child = wexpect.spawn('cat') 174 | self.assertTrue(child.isalive()) 175 | child.sendeof() 176 | child.expect(wexpect.EOF) 177 | assert child.isalive() is False 178 | self.assertFalse(child.isalive()) 179 | 180 | def test_bad_type_in_expect(self): 181 | " expect() does not accept dictionary arguments. " 182 | child = wexpect.spawn('cat') 183 | with self.assertRaises(TypeError): 184 | child.expect({}) 185 | 186 | def test_cwd(self): 187 | " check keyword argument `cwd=' of wexpect.run() " 188 | try: 189 | os.mkdir('cwd_tmp') 190 | except: 191 | pass 192 | tmp_dir = os.path.realpath('cwd_tmp') 193 | child = wexpect.spawn('cmd') 194 | child.expect('>') 195 | child.sendline('cd') 196 | child.expect('>') 197 | default = child.before.splitlines()[1] 198 | child.terminate() 199 | child = wexpect.spawn('cmd', cwd=tmp_dir) 200 | child.expect('>') 201 | child.sendline('cd') 202 | child.expect('>') 203 | pwd_tmp = child.before.splitlines()[1] 204 | child.terminate() 205 | self.assertNotEqual(default, pwd_tmp) 206 | self.assertEqual(tmp_dir, _u(pwd_tmp)) 207 | 208 | def _test_searcher_as(self, searcher, plus=None): 209 | # given, 210 | given_words = ['alpha', 'beta', 'gamma', 'delta', ] 211 | given_search = given_words 212 | if searcher == wexpect.searcher_re: 213 | given_search = [re.compile(word) for word in given_words] 214 | if plus is not None: 215 | given_search = given_search + [plus] 216 | search_string = searcher(given_search) 217 | basic_fmt = '\n {0}: {1}' 218 | fmt = basic_fmt 219 | if searcher is wexpect.searcher_re: 220 | fmt = '\n {0}: re.compile({1})' 221 | expected_output = '{0}:'.format(searcher.__name__) 222 | idx = 0 223 | for word in given_words: 224 | expected_output += fmt.format(idx, '"{0}"'.format(word)) 225 | idx += 1 226 | if plus is not None: 227 | if plus == wexpect.EOF: 228 | expected_output += basic_fmt.format(idx, 'EOF') 229 | elif plus == wexpect.TIMEOUT: 230 | expected_output += basic_fmt.format(idx, 'TIMEOUT') 231 | 232 | # exercise, 233 | self.assertEqual(search_string.__str__(), expected_output) 234 | 235 | def test_searcher_as_string(self): 236 | " check searcher_string(..).__str__() " 237 | self._test_searcher_as(wexpect.searcher_string) 238 | 239 | def test_searcher_as_string_with_EOF(self): 240 | " check searcher_string(..).__str__() that includes EOF " 241 | self._test_searcher_as(wexpect.searcher_string, plus=wexpect.EOF) 242 | 243 | def test_searcher_as_string_with_TIMEOUT(self): 244 | " check searcher_string(..).__str__() that includes TIMEOUT " 245 | self._test_searcher_as(wexpect.searcher_string, plus=wexpect.TIMEOUT) 246 | 247 | def test_searcher_re_as_string(self): 248 | " check searcher_re(..).__str__() " 249 | self._test_searcher_as(wexpect.searcher_re) 250 | 251 | def test_searcher_re_as_string_with_EOF(self): 252 | " check searcher_re(..).__str__() that includes EOF " 253 | self._test_searcher_as(wexpect.searcher_re, plus=wexpect.EOF) 254 | 255 | def test_searcher_re_as_string_with_TIMEOUT(self): 256 | " check searcher_re(..).__str__() that includes TIMEOUT " 257 | self._test_searcher_as(wexpect.searcher_re, plus=wexpect.TIMEOUT) 258 | 259 | def test_exception_tb(self): 260 | " test get_trace() filters away wexpect/__init__.py calls. " 261 | p = wexpect.spawn('sleep 1') 262 | try: 263 | p.expect('BLAH') 264 | except wexpect.ExceptionPexpect as e: 265 | # get_trace should filter out frames in wexpect's own code 266 | tb = e.get_trace() 267 | # exercise, 268 | assert 'raise ' not in tb 269 | assert 'wexpect/__init__.py' not in tb 270 | else: 271 | assert False, "Should have raised an exception." 272 | 273 | if __name__ == '__main__': 274 | unittest.main() 275 | 276 | suite = unittest.makeSuite(TestCaseMisc,'test') 277 | -------------------------------------------------------------------------------- /tests/test_command_list_split.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | from tests import PexpectTestCase 4 | 5 | class TestCaseSplitCommandLine(PexpectTestCase.PexpectTestCase): 6 | def test_split_sizes(self): 7 | self.assertEqual(len(wexpect.split_command_line(r'')), 0) 8 | self.assertEqual(len(wexpect.split_command_line(r'one')), 1) 9 | self.assertEqual(len(wexpect.split_command_line(r'one two')), 2) 10 | self.assertEqual(len(wexpect.split_command_line(r'one two')), 2) 11 | self.assertEqual(len(wexpect.split_command_line(r'one two')), 2) 12 | self.assertEqual(len(wexpect.split_command_line(r'one^ one')), 1) 13 | self.assertEqual(len(wexpect.split_command_line('\'one one\'')), 1) 14 | self.assertEqual(len(wexpect.split_command_line(r'one\"one')), 1) 15 | self.assertEqual(len(wexpect.split_command_line(r"This^' is a^'^ test")), 3) 16 | 17 | def test_join_args(self): 18 | cmd = 'foo bar "b a z"' 19 | cmd2 = wexpect.join_args(wexpect.split_command_line(cmd)) 20 | self.assertEqual(cmd2, cmd) 21 | 22 | cmd = ['foo', 'bar', 'b a z'] 23 | cmd2 = wexpect.split_command_line(wexpect.join_args(cmd)) 24 | self.assertEqual(cmd2, cmd) 25 | 26 | if __name__ == '__main__': 27 | unittest.main() 28 | 29 | suite = unittest.makeSuite(TestCaseSplitCommandLine,'test') 30 | -------------------------------------------------------------------------------- /tests/test_console_reader.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import time 3 | import unittest 4 | import win32process 5 | import win32api 6 | import os 7 | from tests import PexpectTestCase 8 | 9 | 10 | if "RUN_CONSOLE_READER_TEST" not in os.environ: 11 | skip = True 12 | 13 | class ConsoleReaderTestCase(PexpectTestCase.PexpectTestCase): 14 | 15 | @unittest.skipIf(skip, "Skipping test") 16 | def test_console_reader(self): 17 | 18 | 19 | pid = win32process.GetCurrentProcessId() 20 | tid = win32api.GetCurrentThreadId() 21 | args = ['sleep', '1'] 22 | 23 | with self.assertRaises(SystemExit): 24 | wexpect.ConsoleReader(wexpect.join_args(args), tid=tid, pid=pid, cp=1250, logdir='wexpect') 25 | 26 | os.system('cls') 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | 31 | suite = unittest.makeSuite(ConsoleReaderTestCase,'test') 32 | -------------------------------------------------------------------------------- /tests/test_constructor.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | import time 4 | from tests import PexpectTestCase 5 | 6 | class TestCaseConstructor(PexpectTestCase.PexpectTestCase): 7 | def test_constructor (self): 8 | '''This tests that the constructor will work and give 9 | the same results for different styles of invoking __init__(). 10 | This assumes that the root directory / is static during the test. 11 | ''' 12 | p1 = wexpect.spawn('uname -m -n -p -r -s -v', timeout=5) 13 | p1.expect(wexpect.EOF) 14 | time.sleep(p1.delayafterterminate) 15 | p2 = wexpect.spawn('uname', ['-m', '-n', '-p', '-r', '-s', '-v'], timeout=5) 16 | p2.expect(wexpect.EOF) 17 | time.sleep(p1.delayafterterminate) 18 | self.assertEqual(p1.before, p2.before) 19 | self.assertEqual(str(p1).splitlines()[1:9], str(p2).splitlines()[1:9]) 20 | 21 | run_result = wexpect.run('uname -m -n -p -r -s -v') 22 | self.assertEqual(run_result, p2.before) 23 | 24 | def test_named_parameters (self): 25 | '''This tests that named parameters work. 26 | ''' 27 | p = wexpect.spawn('ls',timeout=10) 28 | p.wait() 29 | p = wexpect.spawn(timeout=10, command='ls') 30 | p.wait() 31 | p = wexpect.spawn(args=[], command='ls') 32 | p.wait() 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | 37 | suite = unittest.makeSuite(TestCaseConstructor,'test') 38 | -------------------------------------------------------------------------------- /tests/test_delay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from tests import PexpectTestCase 4 | import wexpect 5 | 6 | 7 | class TestCaseDelay(PexpectTestCase.PexpectTestCase): 8 | """ 9 | Tests for various delay attributes. 10 | """ 11 | def test_delaybeforesend(self): 12 | """ 13 | Test various values for delaybeforesend. 14 | """ 15 | p = wexpect.spawn("cat") 16 | 17 | p.delaybeforesend = 1 18 | p.sendline("line 1") 19 | p.expect("line 1") 20 | 21 | p.delaybeforesend = 0.0 22 | p.sendline("line 2") 23 | p.expect("line 2") 24 | 25 | p.delaybeforesend = None 26 | p.sendline("line 3") 27 | p.expect("line 3") 28 | 29 | def test_delayafterread(self): 30 | """ 31 | Test various values for delayafterread. 32 | """ 33 | p = wexpect.spawn("cat") 34 | 35 | p.delayafterread = 1 36 | p.sendline("line 1") 37 | p.expect("line 1") 38 | 39 | p.delayafterread = 0.0 40 | p.sendline("line 2") 41 | p.expect("line 2") 42 | 43 | p.delayafterread = None 44 | p.sendline("line 3") 45 | p.expect("line 3") 46 | -------------------------------------------------------------------------------- /tests/test_destructor.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | from tests import PexpectTestCase 4 | import gc 5 | import platform 6 | import time 7 | 8 | class TestCaseDestructor(PexpectTestCase.PexpectTestCase): 9 | def test_destructor (self): 10 | if platform.python_implementation() != 'CPython': 11 | # Details of garbage collection are different on other implementations 12 | return 'SKIP' 13 | gc.collect() 14 | time.sleep(2) 15 | p1 = wexpect.spawn('ls', port=4321) 16 | p2 = wexpect.spawn('ls', port=4322) 17 | p3 = wexpect.spawn('ls', port=4323) 18 | p4 = wexpect.spawn('ls', port=4324) 19 | p1.expect(wexpect.EOF) 20 | p2.expect(wexpect.EOF) 21 | p3.expect(wexpect.EOF) 22 | p4.expect(wexpect.EOF) 23 | p1.kill() 24 | p2.kill() 25 | p3.kill() 26 | p4.kill() 27 | p1 = None 28 | p2 = None 29 | p3 = None 30 | p4 = None 31 | gc.collect() 32 | time.sleep(2) # Some platforms are slow at gc... Solaris! 33 | 34 | 35 | p1 = wexpect.spawn('ls', port=4321) 36 | p2 = wexpect.spawn('ls', port=4322) 37 | p3 = wexpect.spawn('ls', port=4323) 38 | p4 = wexpect.spawn('ls', port=4324) 39 | p1.kill() 40 | p2.kill() 41 | p3.kill() 42 | p4.kill() 43 | del (p1) 44 | del (p2) 45 | del (p3) 46 | del (p4) 47 | gc.collect() 48 | time.sleep(2) 49 | 50 | p1 = wexpect.spawn('ls', port=4321) 51 | p2 = wexpect.spawn('ls', port=4322) 52 | p3 = wexpect.spawn('ls', port=4323) 53 | p4 = wexpect.spawn('ls', port=4324) 54 | 55 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 56 | def test_failed_termination(self): 57 | "Test failed termination." 58 | child = wexpect.spawn('cat') 59 | delayafterterminate_orig = child.delayafterterminate 60 | child.delayafterterminate =.1 61 | 62 | msg = 'Child has not been terminated even after it was killed.' 63 | with self.assertRaisesRegex(wexpect.ExceptionPexpect, msg): 64 | child.terminate() 65 | 66 | child.isalive(timeout = delayafterterminate_orig) 67 | 68 | if __name__ == '__main__': 69 | unittest.main() 70 | 71 | suite = unittest.makeSuite(TestCaseDestructor,'test') 72 | -------------------------------------------------------------------------------- /tests/test_echo.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | from tests import PexpectTestCase 4 | 5 | class EchoTestCase(PexpectTestCase.PexpectTestCase): 6 | def testPath(self): 7 | # Path of cmd executable: 8 | cmd_exe = 'cmd' 9 | cmdPrompt = '>' 10 | 11 | # Start the child process 12 | p = wexpect.spawn(cmd_exe) 13 | 14 | # Wait for prompt 15 | p.expect(cmdPrompt) 16 | 17 | # Send a command 18 | p.sendline('echo hello') 19 | p.expect(cmdPrompt) 20 | 21 | self.assertEqual('hello', p.before.splitlines()[1]) 22 | 23 | if __name__ == '__main__': 24 | unittest.main() 25 | 26 | suite = unittest.makeSuite(EchoTestCase,'test') 27 | -------------------------------------------------------------------------------- /tests/test_expect.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import unittest 3 | import subprocess 4 | import time 5 | import signal 6 | import sys 7 | import os 8 | 9 | import wexpect 10 | from tests import PexpectTestCase 11 | 12 | # Many of these test cases blindly assume that sequential directory 13 | # listings of the /bin directory will yield the same results. 14 | # This may not be true, but seems adequate for testing now. 15 | # I should fix this at some point. 16 | 17 | PYTHONBINQUOTE = '"{}"'.format(sys.executable) 18 | FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) 19 | def hex_dump(src, length=16): 20 | result=[] 21 | for i in range(0, len(src), length): 22 | s = src[i:i+length] 23 | hexa = ' '.join(["%02X"%ord(x) for x in s]) 24 | printable = s.translate(FILTER) 25 | result.append("%04X %-*s %s\n" % (i, length*3, hexa, printable)) 26 | return ''.join(result) 27 | 28 | def hex_diff(left, right): 29 | diff = ['< %s\n> %s' % (_left, _right,) for _left, _right in zip( 30 | hex_dump(left).splitlines(), hex_dump(right).splitlines()) 31 | if _left != _right] 32 | return '\n' + '\n'.join(diff,) 33 | 34 | 35 | class ExpectTestCase (PexpectTestCase.PexpectTestCase): 36 | 37 | def test_expect_basic (self): 38 | p = wexpect.spawn('cat', timeout=5) 39 | p.sendline ('Hello') 40 | p.sendline ('there') 41 | p.sendline ('Mr. Python') 42 | p.expect ('Hello') 43 | p.expect ('there') 44 | p.expect ('Mr. Python') 45 | p.sendeof () 46 | p.expect (wexpect.EOF) 47 | 48 | def test_expect_exact_basic (self): 49 | p = wexpect.spawn('cat', timeout=5) 50 | p.sendline ('Hello') 51 | p.sendline ('there') 52 | p.sendline ('Mr. Python') 53 | p.expect_exact ('Hello') 54 | p.expect_exact ('there') 55 | p.expect_exact ('Mr. Python') 56 | p.sendeof () 57 | p.expect_exact (wexpect.EOF) 58 | 59 | def test_expect_ignore_case(self): 60 | '''This test that the ignorecase flag will match patterns 61 | even if case is different using the regex (?i) directive. 62 | ''' 63 | p = wexpect.spawn('cat', timeout=5) 64 | p.sendline ('HELLO') 65 | p.sendline ('there') 66 | p.expect ('(?i)hello') 67 | p.expect ('(?i)THERE') 68 | p.sendeof () 69 | p.expect (wexpect.EOF) 70 | 71 | def test_expect_ignore_case_flag(self): 72 | '''This test that the ignorecase flag will match patterns 73 | even if case is different using the ignorecase flag. 74 | ''' 75 | p = wexpect.spawn('cat', timeout=5) 76 | p.ignorecase = True 77 | p.sendline ('HELLO') 78 | p.sendline ('there') 79 | p.expect ('hello') 80 | p.expect ('THERE') 81 | p.sendeof () 82 | p.expect (wexpect.EOF) 83 | 84 | def test_expect_order (self): 85 | '''This tests that patterns are matched in the same order as given in the pattern_list. 86 | 87 | (Or does it? Doesn't it also pass if expect() always chooses 88 | (one of the) the leftmost matches in the input? -- grahn) 89 | ... agreed! -jquast, the buffer ptr isn't forwarded on match, see first two test cases 90 | ''' 91 | p = wexpect.spawn('cat', timeout=5, echo=True) 92 | self._expect_order(p) 93 | 94 | def test_expect_order_exact (self): 95 | '''Like test_expect_order(), but using expect_exact(). 96 | ''' 97 | p = wexpect.spawn('cat', timeout=5, echo=True) 98 | p.expect = p.expect_exact 99 | self._expect_order(p) 100 | 101 | def _expect_order (self, p): 102 | p.sendline ('1234') 103 | p.sendline ('abcd') 104 | p.sendline ('wxyz') 105 | p.sendline ('7890') 106 | p.sendeof () 107 | for _ in range(2): 108 | index = p.expect ([ 109 | '1234', 110 | 'abcd', 111 | 'wxyz', 112 | wexpect.EOF, 113 | '7890' ]) 114 | self.assertEqual(index, 0, (index, p.before, p.after)) 115 | 116 | for _ in range(2): 117 | index = p.expect ([ 118 | '54321', 119 | wexpect.TIMEOUT, 120 | '1234', 121 | 'abcd', 122 | 'wxyz', 123 | wexpect.EOF], timeout=5) 124 | self.assertEqual(index, 3, (index, p.before, p.after)) 125 | 126 | for _ in range(2): 127 | index = p.expect ([ 128 | '54321', 129 | wexpect.TIMEOUT, 130 | '1234', 131 | 'abcd', 132 | 'wxyz', 133 | wexpect.EOF], timeout=5) 134 | self.assertEqual(index, 4, (index, p.before, p.after)) 135 | 136 | for _ in range(2): 137 | index = p.expect ([ 138 | wexpect.EOF, 139 | 'abcd', 140 | 'wxyz', 141 | '7890' ]) 142 | self.assertEqual(index, 3, (index, p.before, p.after)) 143 | 144 | index = p.expect ([ 145 | 'abcd', 146 | 'wxyz', 147 | '7890', 148 | wexpect.EOF]) 149 | self.assertEqual(index, 3, (index, p.before, p.after)) 150 | 151 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 152 | if wexpect.spawn_class_name == 'SpawnSocket': 153 | p.wait() 154 | 155 | def test_expect_index (self): 156 | '''This tests that mixed list of regex strings, TIMEOUT, and EOF all 157 | return the correct index when matched. 158 | ''' 159 | p = wexpect.spawn('cat', timeout=5, echo=False) 160 | self._expect_index(p) 161 | 162 | def test_expect_index_exact (self): 163 | '''Like test_expect_index(), but using expect_exact(). 164 | ''' 165 | p = wexpect.spawn('cat', timeout=5, echo=False) 166 | p.expect = p.expect_exact 167 | self._expect_index(p) 168 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 169 | if wexpect.spawn_class_name == 'SpawnSocket': 170 | p.wait() 171 | 172 | def _expect_index (self, p): 173 | p.sendline ('1234') 174 | index = p.expect (['abcd','wxyz','1234',wexpect.EOF]) 175 | self.assertEqual(index, 2, "index="+str(index)) 176 | p.sendline ('abcd') 177 | index = p.expect ([wexpect.TIMEOUT,'abcd','wxyz','1234',wexpect.EOF]) 178 | self.assertEqual(index, 1, "index="+str(index)) 179 | p.sendline ('wxyz') 180 | index = p.expect (['54321',wexpect.TIMEOUT,'abcd','wxyz','1234',wexpect.EOF]) 181 | self.assertEqual(index, 3, "index="+str(index)) # Expect 'wxyz' 182 | p.sendline ('$*!@?') 183 | index = p.expect (['54321',wexpect.TIMEOUT,'abcd','wxyz','1234',wexpect.EOF], 184 | timeout=1) 185 | self.assertEqual(index, 1, "index="+str(index)) # Expect TIMEOUT 186 | p.sendeof () 187 | index = p.expect (['54321',wexpect.TIMEOUT,'abcd','wxyz','1234',wexpect.EOF]) 188 | self.assertEqual(index, 5, "index="+str(index)) # Expect EOF 189 | 190 | def test_expect (self): 191 | the_old_way = subprocess.Popen(args=['ls', '-l'], 192 | stdout=subprocess.PIPE).communicate()[0].rstrip() 193 | p = wexpect.spawn('ls -l') 194 | the_new_way = '' 195 | while 1: 196 | i = p.expect (['\n', wexpect.EOF]) 197 | the_new_way = the_new_way + p.before 198 | if i == 1: 199 | break 200 | the_new_way = the_new_way.rstrip() 201 | the_new_way = the_new_way.replace('\r\n', '\n' 202 | ).replace('\r', '\n').replace('\n\n', '\n').rstrip() 203 | the_old_way = the_old_way.decode('utf-8') 204 | the_old_way = the_old_way.replace('\r\n', '\n' 205 | ).replace('\r', '\n').replace('\n\n', '\n').rstrip() 206 | # print(repr(the_old_way)) 207 | # print(repr(the_new_way)) 208 | # the_old_way = the_old_way[0:10000] 209 | # the_new_way = the_new_way[0:10000] 210 | self.assertEqual(the_old_way, the_new_way) 211 | 212 | def test_expect_exact (self): 213 | the_old_way = subprocess.Popen(args=['ls', '-l'], 214 | stdout=subprocess.PIPE).communicate()[0].rstrip() 215 | p = wexpect.spawn('ls -l') 216 | the_new_way = '' 217 | while 1: 218 | i = p.expect_exact (['\n', wexpect.EOF]) 219 | the_new_way = the_new_way + p.before 220 | if i == 1: 221 | break 222 | the_new_way = the_new_way.replace('\r\n', '\n' 223 | ).replace('\r', '\n').replace('\n\n', '\n').rstrip() 224 | the_old_way = the_old_way.decode('utf-8') 225 | the_old_way = the_old_way.replace('\r\n', '\n' 226 | ).replace('\r', '\n').replace('\n\n', '\n').rstrip() 227 | self.assertEqual(the_old_way, the_new_way) 228 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 229 | if wexpect.spawn_class_name == 'SpawnSocket': 230 | p.wait() 231 | 232 | p = wexpect.spawn('echo hello.?world') 233 | i = p.expect_exact('.?') 234 | self.assertEqual(p.before, 'hello') 235 | self.assertEqual(p.after, '.?') 236 | 237 | def test_expect_eof (self): 238 | the_old_way = subprocess.Popen(args=['ls', '-l'], 239 | stdout=subprocess.PIPE).communicate()[0].rstrip() 240 | p = wexpect.spawn('ls -l') 241 | p.expect(wexpect.EOF) # This basically tells it to read everything. Same as wexpect.run() function. 242 | the_new_way = p.before 243 | the_new_way = the_new_way.replace('\r\n', '\n' 244 | ).replace('\r', '\n').replace('\n\n', '\n').rstrip() 245 | the_old_way = the_old_way.decode('utf-8') 246 | the_old_way = the_old_way.replace('\r\n', '\n' 247 | ).replace('\r', '\n').replace('\n\n', '\n').rstrip() 248 | self.assertEqual(the_old_way, the_new_way) 249 | 250 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 251 | if wexpect.spawn_class_name == 'SpawnSocket': 252 | p.wait() 253 | 254 | def test_expect_timeout (self): 255 | p = wexpect.spawn('cat', timeout=5) 256 | p.expect(wexpect.TIMEOUT) # This tells it to wait for timeout. 257 | self.assertEqual(p.after, wexpect.TIMEOUT) 258 | 259 | def test_unexpected_eof (self): 260 | p = wexpect.spawn('ls -l /bin') 261 | try: 262 | p.expect('_Z_XY_XZ') # Probably never see this in ls output. 263 | except wexpect.EOF: 264 | pass 265 | else: 266 | self.fail ('Expected an EOF exception.') 267 | 268 | if wexpect.spawn_class_name == 'SpawnSocket': 269 | p.wait() 270 | 271 | def test_buffer_interface(self): 272 | p = wexpect.spawn('cat', timeout=5) 273 | p.sendline ('Hello') 274 | p.expect ('Hello') 275 | assert len(p.buffer) 276 | p.buffer = 'Testing' 277 | p.sendeof () 278 | if wexpect.spawn_class_name == 'SpawnSocket': 279 | p.wait() 280 | 281 | def _before_after(self, p): 282 | p.timeout = 5 283 | 284 | p.expect('5') 285 | self.assertEqual(p.after, '5') 286 | self.assertTrue(p.before.startswith('[0, 1, 2'), p.before) 287 | 288 | p.expect('50') 289 | self.assertEqual(p.after, '50') 290 | self.assertTrue(p.before.startswith(', 6, 7, 8'), p.before[:20]) 291 | self.assertTrue(p.before.endswith('48, 49, '), p.before[-20:]) 292 | 293 | p.expect(wexpect.EOF) 294 | self.assertEqual(p.after, wexpect.EOF) 295 | assert p.before.startswith(', 51, 52'), p.before[:20] 296 | assert p.before.endswith(', 99]\r\n'), p.before[-20:] 297 | 298 | if wexpect.spawn_class_name == 'SpawnSocket': 299 | p.wait() 300 | 301 | def test_before_after(self): 302 | '''This tests expect() for some simple before/after things. 303 | ''' 304 | p = wexpect.spawn('%s -Wi list100.py' % PYTHONBINQUOTE) 305 | self._before_after(p) 306 | 307 | def test_before_after_exact(self): 308 | '''This tests some simple before/after things, for 309 | expect_exact(). (Grahn broke it at one point.) 310 | ''' 311 | p = wexpect.spawn('%s -Wi list100.py' % PYTHONBINQUOTE) 312 | # mangle the spawn so we test expect_exact() instead 313 | p.expect = p.expect_exact 314 | self._before_after(p) 315 | 316 | def _ordering(self, p): 317 | p.timeout = 20 318 | p.expect('>>> ') 319 | 320 | p.sendline('list(range(4*3))') 321 | self.assertEqual(p.expect(['5,', '5,']), 0) 322 | p.expect('>>> ') 323 | 324 | p.sendline('list(range(4*3))') 325 | self.assertEqual(p.expect(['7,', '5,']), 1) 326 | p.expect('>>> ') 327 | 328 | p.sendline('list(range(4*3))') 329 | self.assertEqual(p.expect(['5,', '7,']), 0) 330 | p.expect('>>> ') 331 | 332 | p.sendline('list(range(4*5))') 333 | self.assertEqual(p.expect(['2,', '12,']), 0) 334 | p.expect('>>> ') 335 | 336 | p.sendline('list(range(4*5))') 337 | self.assertEqual(p.expect(['12,', '2,']), 1) 338 | 339 | p.sendline('exit()') 340 | 341 | def test_ordering(self): 342 | '''This tests expect() for which pattern is returned 343 | when many may eventually match. I (Grahn) am a bit 344 | confused about what should happen, but this test passes 345 | with wexpect 2.1. 346 | ''' 347 | p = wexpect.spawn(PYTHONBINQUOTE) 348 | self._ordering(p) 349 | 350 | def test_ordering_exact(self): 351 | '''This tests expect_exact() for which pattern is returned 352 | when many may eventually match. I (Grahn) am a bit 353 | confused about what should happen, but this test passes 354 | for the expect() method with wexpect 2.1. 355 | ''' 356 | p = wexpect.spawn(PYTHONBINQUOTE) 357 | # mangle the spawn so we test expect_exact() instead 358 | p.expect = p.expect_exact 359 | self._ordering(p) 360 | 361 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 362 | if wexpect.spawn_class_name == 'SpawnSocket': 363 | p.wait() 364 | 365 | def _greed(self, expect): 366 | # End at the same point: the one with the earliest start should win 367 | self.assertEqual(expect(['3, 4', '2, 3, 4']), 1) 368 | 369 | # Start at the same point: first pattern passed wins 370 | self.assertEqual(expect(['5,', '5, 6']), 0) 371 | 372 | # Same pattern passed twice: first instance wins 373 | self.assertEqual(expect(['7, 8', '7, 8, 9', '7, 8']), 0) 374 | 375 | def _greed_read1(self, expect): 376 | # Here, one has an earlier start and a later end. When processing 377 | # one character at a time, the one that finishes first should win, 378 | # because we don't know about the other match when it wins. 379 | # If maxread > 1, this behaviour is currently undefined, although in 380 | # most cases the one that starts first will win. 381 | self.assertEqual(expect(['1, 2, 3', '2,']), 1) 382 | 383 | def test_greed(self): 384 | p = wexpect.spawn(PYTHONBINQUOTE + ' list100.py') 385 | self._greed(p.expect) 386 | 387 | def test_greed_exact(self): 388 | p = wexpect.spawn(PYTHONBINQUOTE + ' list100.py') 389 | self._greed(p.expect_exact) 390 | 391 | def test_bad_arg(self): 392 | p = wexpect.spawn('cat') 393 | with self.assertRaisesRegex(TypeError, '.*must be one of'): 394 | p.expect(1) 395 | with self.assertRaisesRegex(TypeError, '.*must be one of'): 396 | p.expect([1, '2']) 397 | with self.assertRaisesRegex(TypeError, '.*must be one of'): 398 | p.expect_exact(1) 399 | with self.assertRaisesRegex(TypeError, '.*must be one of'): 400 | p.expect_exact([1, '2']) 401 | 402 | def test_timeout_none(self): 403 | p = wexpect.spawn('echo abcdef', timeout=None) 404 | p.expect('abc') 405 | p.expect_exact('def') 406 | p.expect(wexpect.EOF) 407 | 408 | if __name__ == '__main__': 409 | unittest.main() 410 | 411 | suite = unittest.makeSuite(ExpectTestCase, 'test') 412 | -------------------------------------------------------------------------------- /tests/test_interact.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import time 3 | import unittest 4 | from tests import PexpectTestCase 5 | 6 | class InteractTestCase(PexpectTestCase.PexpectTestCase): 7 | 8 | @unittest.skipIf(not (hasattr(wexpect, 'legacy_wexpect')) or (hasattr(wexpect.spawn, 'interact')), "spawn does not support runtime interact switching.") 9 | def test_interact(self): 10 | # Path of cmd executable: 11 | cmd_exe = 'cmd' 12 | cmdPrompt = '>' 13 | 14 | # Start the child process 15 | p = wexpect.spawn(cmd_exe) 16 | 17 | # Wait for prompt 18 | p.expect(cmdPrompt) 19 | 20 | p.interact() 21 | time.sleep(1) 22 | 23 | # Send a command 24 | p.sendline('echo hello') 25 | p.expect(cmdPrompt) 26 | 27 | p.stop_interact() 28 | 29 | self.assertEqual('hello', p.before.splitlines()[1]) 30 | 31 | @unittest.skipIf(not (hasattr(wexpect, 'legacy_wexpect')) or (hasattr(wexpect.spawn, 'interact')), "spawn does not support runtime interact switching.") 32 | def test_interact_dead(self): 33 | # Path of cmd executable: 34 | echo = 'echo hello' 35 | 36 | # Start the child process 37 | p = wexpect.spawn(echo) 38 | 39 | p.expect('hello') 40 | time.sleep(0.5) 41 | 42 | 43 | with self.assertRaises(wexpect.ExceptionPexpect): 44 | p.interact() 45 | 46 | with self.assertRaises(wexpect.ExceptionPexpect): 47 | p.stop_interact() 48 | 49 | if __name__ == '__main__': 50 | unittest.main() 51 | 52 | suite = unittest.makeSuite(InteractTestCase,'test') 53 | -------------------------------------------------------------------------------- /tests/test_isalive.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | import signal 4 | import sys 5 | import time 6 | from tests import PexpectTestCase 7 | 8 | PYBIN = '"{}"'.format(sys.executable) 9 | 10 | class IsAliveTestCase(PexpectTestCase.PexpectTestCase): 11 | """Various tests for the running status of processes.""" 12 | 13 | def test_expect_wait(self): 14 | """Ensure consistency in wait() and isalive().""" 15 | p = wexpect.spawn('sleep 1') 16 | assert p.isalive() 17 | self.assertEqual(p.wait(), 0) 18 | assert not p.isalive() 19 | # In previous versions of ptyprocess/wexpect, calling wait() a second 20 | # time would raise an exception, but not since v4.0 21 | self.assertEqual(p.wait(), 0) 22 | 23 | def test_signal_wait(self): 24 | '''Test calling wait with a process terminated by a signal.''' 25 | if not hasattr(signal, 'SIGALRM'): 26 | return 'SKIP' 27 | p = wexpect.spawn(PYBIN, ['alarm_die.py']) 28 | p.wait() 29 | assert p.exitstatus is None 30 | self.assertEqual(p.signalstatus, signal.SIGALRM) 31 | 32 | def test_expect_isalive_dead_after_normal_termination (self): 33 | p = wexpect.spawn('ls', timeout=15) 34 | p.expect(wexpect.EOF) 35 | assert not p.isalive() 36 | 37 | def test_expect_isalive_dead_after_SIGHUP(self): 38 | p = wexpect.spawn('cat', timeout=5) 39 | assert p.isalive() 40 | self.assertTrue(p.terminate()) 41 | p.expect(wexpect.EOF) 42 | assert not p.isalive() 43 | 44 | def test_expect_isalive_dead_after_SIGINT(self): 45 | p = wexpect.spawn('cat', timeout=5) 46 | assert p.isalive() 47 | force = False 48 | if sys.platform.lower().startswith('sunos'): 49 | # On Solaris (SmartOs), and only when executed from cron(1), SIGKILL 50 | # is required to end the sub-process. This is done using force=True 51 | force = True 52 | self.assertEqual(p.terminate(force), True) 53 | p.expect(wexpect.EOF) 54 | assert not p.isalive() 55 | 56 | def test_expect_isalive_dead_after_SIGKILL(self): 57 | p = wexpect.spawn('cat', timeout=5) 58 | assert p.isalive() 59 | p.kill() 60 | p.expect(wexpect.EOF) 61 | assert not p.isalive() 62 | 63 | def test_forced_terminate(self): 64 | 65 | p = wexpect.spawn(PYBIN + ' needs_kill.py') 66 | p.expect('READY') 67 | self.assertEqual(p.terminate(force=True), True) 68 | p.expect(wexpect.EOF) 69 | assert not p.isalive() 70 | 71 | ### Some platforms allow this. Some reset status after call to waitpid. 72 | ### probably not necessary, isalive() returns early when terminate is False. 73 | def test_expect_isalive_consistent_multiple_calls (self): 74 | '''This tests that multiple calls to isalive() return same value. 75 | ''' 76 | p = wexpect.spawn('cat') 77 | assert p.isalive() 78 | assert p.isalive() 79 | p.sendeof() 80 | p.expect(wexpect.EOF) 81 | assert not p.isalive() 82 | assert not p.isalive() 83 | 84 | if __name__ == '__main__': 85 | unittest.main() 86 | 87 | suite = unittest.makeSuite(IsAliveTestCase, 'test') 88 | -------------------------------------------------------------------------------- /tests/test_long.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | import sys 4 | import os 5 | from tests import PexpectTestCase 6 | from . import long_printer 7 | 8 | puskas_wiki = long_printer.puskas_wiki 9 | 10 | class TestCaseLong(PexpectTestCase.PexpectTestCase): 11 | def test_long (self): 12 | 13 | here = os.path.dirname(os.path.abspath(__file__)) 14 | sys.path.insert(0, here) 15 | 16 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 17 | python_executable = '"' + sys.executable + '" ' 18 | child_script = here + '\\long_printer.py' 19 | 20 | 21 | longPrinter = python_executable + ' ' + child_script 22 | prompt = 'puskas> ' 23 | 24 | # Start the child process 25 | p = wexpect.spawn(longPrinter) 26 | # Wait for prompt 27 | p.expect(prompt) 28 | 29 | for i in range(10): 30 | p.sendline('0') 31 | p.expect(prompt) 32 | self.assertEqual(p.before.splitlines()[1], puskas_wiki[0]) 33 | 34 | p.sendline('all') 35 | p.expect(prompt) 36 | for a,b in zip(p.before.splitlines()[1:], puskas_wiki): 37 | self.assertEqual(a, b) 38 | 39 | for j, paragraph in enumerate(puskas_wiki): 40 | p.sendline(str(j)) 41 | p.expect(prompt) 42 | self.assertEqual(p.before.splitlines()[1], paragraph) 43 | 44 | 45 | if __name__ == '__main__': 46 | unittest.main() 47 | 48 | suite = unittest.makeSuite(TestCaseLong,'test') 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import re 4 | import os 5 | import time 6 | 7 | import wexpect 8 | from tests import PexpectTestCase 9 | 10 | # the program cat(1) may display ^D\x08\x08 when \x04 (EOF, Ctrl-D) is sent 11 | _CAT_EOF = '^D\x08\x08' 12 | 13 | def _u(s): 14 | return s 15 | 16 | PYBIN = '"{}"'.format(sys.executable) 17 | 18 | class TestCaseMisc(PexpectTestCase.PexpectTestCase): 19 | 20 | def test_wrong_command(self): 21 | "Test raise of exception in case of wrong program" 22 | with self.assertRaisesRegex(wexpect.ExceptionPexpect,"The command was not found.+"): 23 | child = wexpect.spawn('unexecutable') 24 | 25 | def test_isatty(self): 26 | " Test isatty() is True after spawning process on most platforms. " 27 | child = wexpect.spawn('cat') 28 | assert child.isatty() 29 | 30 | def test_read(self): 31 | " Test spawn.read by calls of various size. " 32 | child = wexpect.spawn('cat') 33 | child.sendline("abc") 34 | child.sendeof() 35 | child.readline() 36 | self.assertEqual(child.read(0), '') 37 | self.assertEqual(child.read(1), 'a') 38 | self.assertEqual(child.read(1), 'b') 39 | self.assertEqual(child.read(1), 'c') 40 | self.assertEqual(child.read(2), '\r\n') 41 | 42 | def test_readline_bin_echo(self): 43 | " Test spawn('echo'). " 44 | # given, 45 | child = wexpect.spawn('echo', ['alpha', 'beta']) 46 | 47 | # exercise, 48 | self.assertEqual(child.readline(), 'alpha beta\r\n') 49 | 50 | def test_readline(self): 51 | " Test spawn.readline(). " 52 | # when argument 0 is sent, nothing is returned. 53 | # Otherwise the argument value is meaningless. 54 | child = wexpect.spawn('cat') 55 | child.sendline("alpha") 56 | child.sendline("beta") 57 | child.sendline("gamma") 58 | child.sendline("delta") 59 | child.sendeof() 60 | self.assertEqual(child.readline(0), '') 61 | child.readline().rstrip() 62 | self.assertEqual(child.readline().rstrip(), 'alpha') 63 | child.readline().rstrip() 64 | self.assertEqual(child.readline(1).rstrip(), 'beta') 65 | child.readline().rstrip() 66 | self.assertEqual(child.readline(2).rstrip(), 'gamma') 67 | child.readline().rstrip() 68 | self.assertEqual(child.readline().rstrip(), 'delta') 69 | child.expect(wexpect.EOF) 70 | if type(child).__name__ in ['SpawnPipe', 'SpawnSocket']: 71 | time.sleep(child.delayafterterminate) 72 | assert not child.isalive(trust_console=False) 73 | else: 74 | assert not child.isalive() 75 | self.assertEqual(child.exitstatus, 0) 76 | 77 | def test_iter(self): 78 | " iterating over lines of spawn.__iter__(). " 79 | child = wexpect.spawn('echo "abc\r\n123"') 80 | # Don't use ''.join() because we want to test __iter__(). 81 | page = '' 82 | for line in child: 83 | page += line 84 | page = page.replace(_CAT_EOF, '') 85 | self.assertEqual(page, 'abc\r\n123\r\n') 86 | 87 | def test_readlines(self): 88 | " reading all lines of spawn.readlines(). " 89 | child = wexpect.spawn('cat', echo=False) 90 | child.sendline("abc") 91 | child.sendline("123") 92 | child.sendeof() 93 | page = ''.join(child.readlines()).replace(_CAT_EOF, '') 94 | self.assertEqual(page, '\r\nabc\r\n\r\n123\r\n') 95 | child.expect(wexpect.EOF) 96 | if type(child).__name__ in ['SpawnPipe', 'SpawnSocket']: 97 | time.sleep(child.delayafterterminate) 98 | assert not child.isalive(trust_console=False) 99 | else: 100 | assert not child.isalive() 101 | self.assertEqual(child.exitstatus, 0) 102 | 103 | def test_write(self): 104 | " write a character and return it in return. " 105 | child = wexpect.spawn('cat') 106 | child.write('a') 107 | child.write('\r') 108 | child.readline() 109 | self.assertEqual(child.readline(), 'a\r\n') 110 | 111 | def test_writelines(self): 112 | " spawn.writelines() " 113 | child = wexpect.spawn('cat') 114 | # notice that much like file.writelines, we do not delimit by newline 115 | # -- it is equivalent to calling write(''.join([args,])) 116 | child.writelines(['abc', '123', 'xyz', '\r']) 117 | child.sendeof() 118 | child.readline() 119 | line = child.readline() 120 | self.assertEqual(line, 'abc123xyz\r\n') 121 | 122 | def test_eof(self): 123 | " call to expect() after EOF is received raises wexpect.EOF " 124 | child = wexpect.spawn('cat') 125 | child.sendeof() 126 | with self.assertRaises(wexpect.EOF): 127 | child.expect('the unexpected') 128 | 129 | def test_with(self): 130 | "spawn can be used as a context manager" 131 | with wexpect.spawn(PYBIN + ' echo_w_prompt.py') as p: 132 | p.expect('') 133 | p.sendline('alpha') 134 | p.expect('alpha') 135 | assert p.isalive() 136 | 137 | assert not p.isalive() 138 | 139 | def test_terminate(self): 140 | " test force terminate always succeeds (SIGKILL). " 141 | child = wexpect.spawn('cat') 142 | child.terminate(force=1) 143 | assert child.terminated 144 | 145 | def test_bad_arguments_suggest_fdpsawn(self): 146 | " assert custom exception for spawn(int). " 147 | expect_errmsg = "maybe you want to use fdpexpect.fdspawn" 148 | with self.assertRaisesRegex(wexpect.ExceptionPexpect, 149 | ".*" + expect_errmsg): 150 | wexpect.spawn(1) 151 | 152 | def test_bad_arguments_second_arg_is_list(self): 153 | " Second argument to spawn, if used, must be only a list." 154 | with self.assertRaises(TypeError): 155 | wexpect.spawn('ls', '-la') 156 | 157 | with self.assertRaises(TypeError): 158 | # not even a tuple, 159 | wexpect.spawn('ls', ('-la',)) 160 | 161 | def test_read_after_close_raises_value_error(self): 162 | " Calling read_nonblocking after close raises ValueError. " 163 | # as read_nonblocking underlies all other calls to read, 164 | # ValueError should be thrown for all forms of read. 165 | with self.assertRaises(ValueError): 166 | p = wexpect.spawn('cat') 167 | p.close() 168 | p.read_nonblocking() 169 | 170 | with self.assertRaises(ValueError): 171 | p = wexpect.spawn('cat') 172 | p.close() 173 | p.read() 174 | 175 | with self.assertRaises(ValueError): 176 | p = wexpect.spawn('cat') 177 | p.close() 178 | p.readline() 179 | 180 | with self.assertRaises(ValueError): 181 | p = wexpect.spawn('cat') 182 | p.close() 183 | p.readlines() 184 | 185 | def test_isalive(self): 186 | " check isalive() before and after EOF. (True, False) " 187 | child = wexpect.spawn('cat') 188 | self.assertTrue(child.isalive()) 189 | child.sendeof() 190 | child.expect(wexpect.EOF) 191 | assert child.isalive() is False 192 | self.assertFalse(child.isalive()) 193 | 194 | def test_bad_type_in_expect(self): 195 | " expect() does not accept dictionary arguments. " 196 | child = wexpect.spawn('cat') 197 | with self.assertRaises(TypeError): 198 | child.expect({}) 199 | 200 | def test_cwd(self): 201 | " check keyword argument `cwd=' of wexpect.run() " 202 | try: 203 | os.mkdir('cwd_tmp') 204 | except: 205 | pass 206 | tmp_dir = os.path.realpath('cwd_tmp') 207 | child = wexpect.spawn('cmd') 208 | child.expect('>') 209 | child.sendline('cd') 210 | child.expect('>') 211 | default = child.before.splitlines()[1] 212 | child.terminate() 213 | child = wexpect.spawn('cmd', cwd=tmp_dir) 214 | child.expect('>') 215 | child.sendline('cd') 216 | child.expect('>') 217 | pwd_tmp = child.before.splitlines()[1] 218 | child.terminate() 219 | self.assertNotEqual(default, pwd_tmp) 220 | self.assertEqual(tmp_dir, _u(pwd_tmp)) 221 | 222 | def _test_searcher_as(self, searcher, plus=None): 223 | # given, 224 | given_words = ['alpha', 'beta', 'gamma', 'delta', ] 225 | given_search = given_words 226 | if searcher == wexpect.searcher_re: 227 | given_search = [re.compile(word) for word in given_words] 228 | if plus is not None: 229 | given_search = given_search + [plus] 230 | search_string = searcher(given_search) 231 | basic_fmt = '\n {0}: {1}' 232 | fmt = basic_fmt 233 | if searcher is wexpect.searcher_re: 234 | fmt = '\n {0}: re.compile({1})' 235 | expected_output = '{0}:'.format(searcher.__name__) 236 | idx = 0 237 | for word in given_words: 238 | expected_output += fmt.format(idx, '"{0}"'.format(word)) 239 | idx += 1 240 | if plus is not None: 241 | if plus == wexpect.EOF: 242 | expected_output += basic_fmt.format(idx, 'EOF') 243 | elif plus == wexpect.TIMEOUT: 244 | expected_output += basic_fmt.format(idx, 'TIMEOUT') 245 | 246 | # exercise, 247 | self.assertEqual(search_string.__str__(), expected_output) 248 | 249 | def test_searcher_as_string(self): 250 | " check searcher_string(..).__str__() " 251 | self._test_searcher_as(wexpect.searcher_string) 252 | 253 | def test_searcher_as_string_with_EOF(self): 254 | " check searcher_string(..).__str__() that includes EOF " 255 | self._test_searcher_as(wexpect.searcher_string, plus=wexpect.EOF) 256 | 257 | def test_searcher_as_string_with_TIMEOUT(self): 258 | " check searcher_string(..).__str__() that includes TIMEOUT " 259 | self._test_searcher_as(wexpect.searcher_string, plus=wexpect.TIMEOUT) 260 | 261 | def test_searcher_re_as_string(self): 262 | " check searcher_re(..).__str__() " 263 | self._test_searcher_as(wexpect.searcher_re) 264 | 265 | def test_searcher_re_as_string_with_EOF(self): 266 | " check searcher_re(..).__str__() that includes EOF " 267 | self._test_searcher_as(wexpect.searcher_re, plus=wexpect.EOF) 268 | 269 | def test_searcher_re_as_string_with_TIMEOUT(self): 270 | " check searcher_re(..).__str__() that includes TIMEOUT " 271 | self._test_searcher_as(wexpect.searcher_re, plus=wexpect.TIMEOUT) 272 | 273 | def test_exception_tb(self): 274 | " test get_trace() filters away wexpect/__init__.py calls. " 275 | p = wexpect.spawn('sleep 1') 276 | try: 277 | p.expect('BLAH') 278 | except wexpect.ExceptionPexpect as e: 279 | # get_trace should filter out frames in wexpect's own code 280 | tb = e.get_trace() 281 | # exercise, 282 | assert 'raise ' not in tb 283 | assert 'wexpect/__init__.py' not in tb 284 | else: 285 | assert False, "Should have raised an exception." 286 | 287 | if __name__ == '__main__': 288 | unittest.main() 289 | 290 | suite = unittest.makeSuite(TestCaseMisc,'test') 291 | -------------------------------------------------------------------------------- /tests/test_misc_console_coverage.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import re 4 | import os 5 | import time 6 | 7 | import wexpect 8 | from tests import PexpectTestCase 9 | 10 | # the program cat(1) may display ^D\x08\x08 when \x04 (EOF, Ctrl-D) is sent 11 | _CAT_EOF = '^D\x08\x08' 12 | 13 | def _u(s): 14 | return s 15 | 16 | PYBIN = '"{}"'.format(sys.executable) 17 | 18 | class TestCaseMisc(PexpectTestCase.PexpectTestCase): 19 | 20 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 21 | def test_isatty(self): 22 | " Test isatty() is True after spawning process on most platforms. " 23 | child = wexpect.spawn('cat', coverage_console_reader=True) 24 | if not child.isatty() and sys.platform.lower().startswith('sunos'): 25 | if hasattr(unittest, 'SkipTest'): 26 | raise unittest.SkipTest("Not supported on this platform.") 27 | return 'skip' 28 | assert child.isatty() 29 | 30 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 31 | def test_read(self): 32 | " Test spawn.read by calls of various size. " 33 | child = wexpect.spawn('cat', coverage_console_reader=True) 34 | child.sendline("abc") 35 | child.sendeof() 36 | child.readline() 37 | self.assertEqual(child.read(0), '') 38 | self.assertEqual(child.read(1), 'a') 39 | self.assertEqual(child.read(1), 'b') 40 | self.assertEqual(child.read(1), 'c') 41 | self.assertEqual(child.read(2), '\r\n') 42 | 43 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 44 | def test_readline_bin_echo(self): 45 | " Test spawn('echo'). " 46 | # given, 47 | child = wexpect.spawn('echo', ['alpha', 'beta'], coverage_console_reader=True) 48 | 49 | # exercise, 50 | self.assertEqual(child.readline(), 'alpha beta\r\n') 51 | 52 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 53 | def test_readline(self): 54 | " Test spawn.readline(). " 55 | # when argument 0 is sent, nothing is returned. 56 | # Otherwise the argument value is meaningless. 57 | child = wexpect.spawn('cat', coverage_console_reader=True) 58 | child.sendline("alpha") 59 | child.sendline("beta") 60 | child.sendline("gamma") 61 | child.sendline("delta") 62 | child.sendeof() 63 | self.assertEqual(child.readline(0), '') 64 | child.readline().rstrip() 65 | self.assertEqual(child.readline().rstrip(), 'alpha') 66 | child.readline().rstrip() 67 | self.assertEqual(child.readline(1).rstrip(), 'beta') 68 | child.readline().rstrip() 69 | self.assertEqual(child.readline(2).rstrip(), 'gamma') 70 | child.readline().rstrip() 71 | self.assertEqual(child.readline().rstrip(), 'delta') 72 | child.expect(wexpect.EOF) 73 | if type(child).__name__ in ['SpawnPipe', 'SpawnSocket']: 74 | time.sleep(child.delayafterterminate) 75 | assert not child.isalive(trust_console=False) 76 | else: 77 | assert not child.isalive() 78 | self.assertEqual(child.exitstatus, 0) 79 | 80 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 81 | def test_iter(self): 82 | " iterating over lines of spawn.__iter__(). " 83 | child = wexpect.spawn('echo "abc\r\n123"', coverage_console_reader=True) 84 | # Don't use ''.join() because we want to test __iter__(). 85 | page = '' 86 | for line in child: 87 | page += line 88 | page = page.replace(_CAT_EOF, '') 89 | self.assertEqual(page, 'abc\r\n123\r\n') 90 | 91 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 92 | def test_readlines(self): 93 | " reading all lines of spawn.readlines(). " 94 | child = wexpect.spawn('cat', echo=False, coverage_console_reader=True) 95 | child.sendline("abc") 96 | child.sendline("123") 97 | child.sendeof() 98 | page = ''.join(child.readlines()).replace(_CAT_EOF, '') 99 | self.assertEqual(page, '\r\nabc\r\n\r\n123\r\n') 100 | child.expect(wexpect.EOF) 101 | if type(child).__name__ in ['SpawnPipe', 'SpawnSocket']: 102 | time.sleep(child.delayafterterminate) 103 | assert not child.isalive(trust_console=False) 104 | else: 105 | assert not child.isalive() 106 | self.assertEqual(child.exitstatus, 0) 107 | 108 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 109 | def test_write(self): 110 | " write a character and return it in return. " 111 | child = wexpect.spawn('cat', coverage_console_reader=True) 112 | child.write('a') 113 | child.write('\r') 114 | child.readline() 115 | self.assertEqual(child.readline(), 'a\r\n') 116 | 117 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 118 | def test_writelines(self): 119 | " spawn.writelines() " 120 | child = wexpect.spawn('cat', coverage_console_reader=True) 121 | # notice that much like file.writelines, we do not delimit by newline 122 | # -- it is equivalent to calling write(''.join([args,])) 123 | child.writelines(['abc', '123', 'xyz', '\r']) 124 | child.sendeof() 125 | child.readline() 126 | line = child.readline() 127 | self.assertEqual(line, 'abc123xyz\r\n') 128 | 129 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 130 | def test_eof(self): 131 | " call to expect() after EOF is received raises wexpect.EOF " 132 | child = wexpect.spawn('cat', coverage_console_reader=True) 133 | child.sendeof() 134 | with self.assertRaises(wexpect.EOF): 135 | child.expect('the unexpected') 136 | 137 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 138 | def test_with(self): 139 | "spawn can be used as a context manager" 140 | with wexpect.spawn(PYBIN + ' echo_w_prompt.py', coverage_console_reader=True) as p: 141 | p.expect('') 142 | p.sendline('alpha') 143 | p.expect('alpha') 144 | assert p.isalive() 145 | 146 | assert not p.isalive() 147 | 148 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 149 | def test_terminate(self): 150 | " test force terminate always succeeds (SIGKILL). " 151 | child = wexpect.spawn('cat', coverage_console_reader=True) 152 | child.terminate(force=1) 153 | assert child.terminated 154 | 155 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 156 | def test_bad_arguments_suggest_fdpsawn(self): 157 | " assert custom exception for spawn(int). " 158 | expect_errmsg = "maybe you want to use fdpexpect.fdspawn" 159 | with self.assertRaisesRegex(wexpect.ExceptionPexpect, 160 | ".*" + expect_errmsg): 161 | wexpect.spawn(1, coverage_console_reader=True) 162 | 163 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 164 | def test_bad_arguments_second_arg_is_list(self): 165 | " Second argument to spawn, if used, must be only a list." 166 | with self.assertRaises(TypeError): 167 | wexpect.spawn('ls', '-la', coverage_console_reader=True) 168 | 169 | with self.assertRaises(TypeError): 170 | # not even a tuple, 171 | wexpect.spawn('ls', ('-la',), coverage_console_reader=True) 172 | 173 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 174 | def test_read_after_close_raises_value_error(self): 175 | " Calling read_nonblocking after close raises ValueError. " 176 | # as read_nonblocking underlies all other calls to read, 177 | # ValueError should be thrown for all forms of read. 178 | with self.assertRaises(ValueError): 179 | p = wexpect.spawn('cat', coverage_console_reader=True) 180 | p.close() 181 | p.read_nonblocking() 182 | 183 | with self.assertRaises(ValueError): 184 | p = wexpect.spawn('cat', coverage_console_reader=True) 185 | p.close() 186 | p.read() 187 | 188 | with self.assertRaises(ValueError): 189 | p = wexpect.spawn('cat', coverage_console_reader=True) 190 | p.close() 191 | p.readline() 192 | 193 | with self.assertRaises(ValueError): 194 | p = wexpect.spawn('cat', coverage_console_reader=True) 195 | p.close() 196 | p.readlines() 197 | 198 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 199 | def test_isalive(self): 200 | " check isalive() before and after EOF. (True, False) " 201 | child = wexpect.spawn('cat', coverage_console_reader=True) 202 | self.assertTrue(child.isalive()) 203 | child.sendeof() 204 | child.expect(wexpect.EOF) 205 | assert child.isalive() is False 206 | self.assertFalse(child.isalive()) 207 | 208 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 209 | def test_bad_type_in_expect(self): 210 | " expect() does not accept dictionary arguments. " 211 | child = wexpect.spawn('cat', coverage_console_reader=True) 212 | with self.assertRaises(TypeError): 213 | child.expect({}) 214 | 215 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 216 | def test_cwd(self): 217 | " check keyword argument `cwd=' of wexpect.run() " 218 | try: 219 | os.mkdir('cwd_tmp') 220 | except: 221 | pass 222 | tmp_dir = os.path.realpath('cwd_tmp') 223 | child = wexpect.spawn('cmd', coverage_console_reader=True) 224 | child.expect('>') 225 | child.sendline('cd') 226 | child.expect('>') 227 | default = child.before.splitlines()[1] 228 | child.terminate() 229 | child = wexpect.spawn('cmd', cwd=tmp_dir, coverage_console_reader=True) 230 | child.expect('>') 231 | child.sendline('cd') 232 | child.expect('>') 233 | pwd_tmp = child.before.splitlines()[1] 234 | child.terminate() 235 | self.assertNotEqual(default, pwd_tmp) 236 | self.assertEqual(tmp_dir, _u(pwd_tmp)) 237 | 238 | def _test_searcher_as(self, searcher, plus=None): 239 | # given, 240 | given_words = ['alpha', 'beta', 'gamma', 'delta', ] 241 | given_search = given_words 242 | if searcher == wexpect.searcher_re: 243 | given_search = [re.compile(word) for word in given_words] 244 | if plus is not None: 245 | given_search = given_search + [plus] 246 | search_string = searcher(given_search) 247 | basic_fmt = '\n {0}: {1}' 248 | fmt = basic_fmt 249 | if searcher is wexpect.searcher_re: 250 | fmt = '\n {0}: re.compile({1})' 251 | expected_output = '{0}:'.format(searcher.__name__) 252 | idx = 0 253 | for word in given_words: 254 | expected_output += fmt.format(idx, '"{0}"'.format(word)) 255 | idx += 1 256 | if plus is not None: 257 | if plus == wexpect.EOF: 258 | expected_output += basic_fmt.format(idx, 'EOF') 259 | elif plus == wexpect.TIMEOUT: 260 | expected_output += basic_fmt.format(idx, 'TIMEOUT') 261 | 262 | # exercise, 263 | self.assertEqual(search_string.__str__(), expected_output) 264 | 265 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 266 | def test_searcher_as_string(self): 267 | " check searcher_string(..).__str__() " 268 | self._test_searcher_as(wexpect.searcher_string) 269 | 270 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 271 | def test_searcher_as_string_with_EOF(self): 272 | " check searcher_string(..).__str__() that includes EOF " 273 | self._test_searcher_as(wexpect.searcher_string, plus=wexpect.EOF) 274 | 275 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 276 | def test_searcher_as_string_with_TIMEOUT(self): 277 | " check searcher_string(..).__str__() that includes TIMEOUT " 278 | self._test_searcher_as(wexpect.searcher_string, plus=wexpect.TIMEOUT) 279 | 280 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 281 | def test_searcher_re_as_string(self): 282 | " check searcher_re(..).__str__() " 283 | self._test_searcher_as(wexpect.searcher_re) 284 | 285 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 286 | def test_searcher_re_as_string_with_EOF(self): 287 | " check searcher_re(..).__str__() that includes EOF " 288 | self._test_searcher_as(wexpect.searcher_re, plus=wexpect.EOF) 289 | 290 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 291 | def test_searcher_re_as_string_with_TIMEOUT(self): 292 | " check searcher_re(..).__str__() that includes TIMEOUT " 293 | self._test_searcher_as(wexpect.searcher_re, plus=wexpect.TIMEOUT) 294 | 295 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 296 | def test_exception_tb(self): 297 | " test get_trace() filters away wexpect/__init__.py calls. " 298 | p = wexpect.spawn('sleep 1', coverage_console_reader=True) 299 | try: 300 | p.expect('BLAH') 301 | except wexpect.ExceptionPexpect as e: 302 | # get_trace should filter out frames in wexpect's own code 303 | tb = e.get_trace() 304 | # exercise, 305 | assert 'raise ' not in tb 306 | assert 'wexpect/__init__.py' not in tb 307 | else: 308 | assert False, "Should have raised an exception." 309 | 310 | if __name__ == '__main__': 311 | unittest.main() 312 | 313 | suite = unittest.makeSuite(TestCaseMisc,'test') 314 | -------------------------------------------------------------------------------- /tests/test_parametric_printer.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | import sys 4 | import os 5 | import time 6 | from tests import PexpectTestCase 7 | 8 | class TestCaseParametricPrinter(PexpectTestCase.PexpectTestCase): 9 | def test_all_line_length (self): 10 | 11 | here = os.path.dirname(os.path.abspath(__file__)) 12 | sys.path.insert(0, here) 13 | 14 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 15 | python_executable = '"' + sys.executable + '" ' 16 | child_script = here + '\\parametric_printer.py' 17 | 18 | self.prompt = '> ' 19 | 20 | # Start the child process 21 | self.p = wexpect.spawn(python_executable + ' ' + child_script) 22 | # Wait for prompt 23 | self.p.expect(self.prompt) 24 | 25 | self._test(['a'], range(1,200), [1], [0]) 26 | 27 | self.p.terminate() 28 | 29 | def test_random(self): 30 | 31 | here = os.path.dirname(os.path.abspath(__file__)) 32 | sys.path.insert(0, here) 33 | 34 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 35 | python_executable = '"' + sys.executable + '" ' 36 | child_script = here + '\\parametric_printer.py' 37 | 38 | self.prompt = '> ' 39 | 40 | # Start the child process 41 | self.p = wexpect.spawn(python_executable + ' ' + child_script) 42 | # Wait for prompt 43 | self.p.expect(self.prompt) 44 | 45 | self._test(['a', 'b', 'c'], [1, 2, 4, 8], [1, 2, 4, 8], [-1, 0, 1, 2]) 46 | self._test(['a', 'b', 'c'], [16], [16], [-1, 0, 1]) 47 | self._test(['a', 'b', 'c'], [16, 32, 64], [16, 32, 64], [-1, 0]) 48 | 49 | self.p.terminate() 50 | 51 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy has bug around refreshing long consoles") 52 | def test_long_console(self): 53 | 54 | here = os.path.dirname(os.path.abspath(__file__)) 55 | sys.path.insert(0, here) 56 | 57 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 58 | python_executable = '"' + sys.executable + '" ' 59 | child_script = here + '\\parametric_printer.py' 60 | 61 | self.prompt = '> ' 62 | 63 | # Start the child process 64 | self.p = wexpect.spawn(python_executable + ' ' + child_script) 65 | # Wait for prompt 66 | self.p.expect(self.prompt) 67 | 68 | self._test(['a', 'b', 'c', 'd', 'e', 'f'], [8, 16, 32, 64], [64, 128, 256], [-1, 0]) 69 | 70 | self.p.terminate() 71 | 72 | def _test(self, character_list, character_count_list, line_count_list, speed_ms_list): 73 | 74 | # print(f'character_list: {character_list} character_count_list: {character_count_list} line_count_list: {line_count_list} speed_ms_list: {speed_ms_list}') 75 | for character in character_list: 76 | for character_count in character_count_list: 77 | for line_count in line_count_list: 78 | for speed_ms in speed_ms_list: 79 | command = f'{character},{character_count},{line_count},{speed_ms}' 80 | self.p.sendline(command) 81 | self.p.expect(self.prompt) 82 | expected = [character*character_count] * line_count 83 | try: 84 | self.assertEqual(self.p.before.splitlines()[1:-1], expected) 85 | except: 86 | raise 87 | 88 | if __name__ == '__main__': 89 | unittest.main() 90 | 91 | suite = unittest.makeSuite(TestCaseParametricPrinter,'test') 92 | -------------------------------------------------------------------------------- /tests/test_parametric_printer_coverage.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | import sys 4 | import os 5 | import time 6 | from tests import PexpectTestCase 7 | 8 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 9 | class TestCaseParametricPrinter(PexpectTestCase.PexpectTestCase): 10 | def test_all_line_length (self): 11 | 12 | here = os.path.dirname(os.path.abspath(__file__)) 13 | sys.path.insert(0, here) 14 | 15 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 16 | python_executable = '"' + sys.executable + '" ' 17 | child_script = here + '\\parametric_printer.py' 18 | 19 | self.prompt = '> ' 20 | 21 | # Start the child process 22 | self.p = wexpect.spawn(python_executable + ' ' + child_script, coverage_console_reader=True) 23 | # Wait for prompt 24 | self.p.expect(self.prompt) 25 | 26 | self._test(['a'], range(1,200), [1], [0]) 27 | 28 | self.p.terminate() 29 | 30 | def test_long_console(self): 31 | 32 | here = os.path.dirname(os.path.abspath(__file__)) 33 | sys.path.insert(0, here) 34 | 35 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 36 | python_executable = '"' + sys.executable + '" ' 37 | child_script = here + '\\parametric_printer.py' 38 | 39 | self.prompt = '> ' 40 | 41 | # Start the child process 42 | self.p = wexpect.spawn(python_executable + ' ' + child_script, coverage_console_reader=True) 43 | # Wait for prompt 44 | self.p.expect(self.prompt) 45 | 46 | self._test(['a', 'b', 'c', 'd', 'e', 'f'], [8, 16, 32, 64], [64, 128, 256], [-1, 0]) 47 | 48 | self.p.terminate() 49 | 50 | def _test(self, character_list, character_count_list, line_count_list, speed_ms_list): 51 | 52 | # print(f'character_list: {character_list} character_count_list: {character_count_list} line_count_list: {line_count_list} speed_ms_list: {speed_ms_list}') 53 | for character in character_list: 54 | for character_count in character_count_list: 55 | for line_count in line_count_list: 56 | for speed_ms in speed_ms_list: 57 | command = f'{character},{character_count},{line_count},{speed_ms}' 58 | self.p.sendline(command) 59 | self.p.expect(self.prompt) 60 | expected = [character*character_count] * line_count 61 | try: 62 | self.assertEqual(self.p.before.splitlines()[1:-1], expected) 63 | except: 64 | raise 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | 69 | suite = unittest.makeSuite(TestCaseParametricPrinter,'test') 70 | -------------------------------------------------------------------------------- /tests/test_readline.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import time 3 | import sys 4 | import os 5 | import unittest 6 | from tests import PexpectTestCase 7 | 8 | here = os.path.dirname(os.path.abspath(__file__)) 9 | sys.path.insert(0, here) 10 | 11 | print(wexpect.__version__) 12 | 13 | # With quotes (C:\Program Files\Python37\python.exe needs quotes) 14 | python_executable = '"' + sys.executable + '" ' 15 | child_script = here + '\\lines_printer.py' 16 | 17 | class ReadLineTestCase(PexpectTestCase.PexpectTestCase): 18 | def testReadline(self): 19 | fooPath = python_executable + ' ' + child_script 20 | prompt = ': ' 21 | num = 5 22 | 23 | # Start the child process 24 | p = wexpect.spawn(fooPath) 25 | # Wait for prompt 26 | p.expect(prompt) 27 | p.sendline(str(num)) 28 | p.expect('Bye!\r\n') 29 | expected_lines = p.before.splitlines(True) # Keep the line end 30 | expected_lines += [p.match.group()] 31 | 32 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 33 | if wexpect.spawn_class_name == 'SpawnSocket': 34 | p.wait() 35 | 36 | # Start the child process 37 | p = wexpect.spawn(fooPath) 38 | # Wait for prompt 39 | p.expect(prompt) 40 | 41 | p.sendline(str(num)) 42 | for i in range(num +2): # +1 the line of sendline +1: Bye 43 | line = p.readline() 44 | self.assertEqual(expected_lines[i], line) 45 | 46 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 47 | if wexpect.spawn_class_name == 'SpawnSocket': 48 | p.wait() 49 | 50 | # Start the child process 51 | p = wexpect.spawn(fooPath) 52 | # Wait for prompt 53 | p.expect(prompt) 54 | 55 | p.sendline(str(num)) 56 | readlines_lines = p.readlines() 57 | self.assertEqual(expected_lines, readlines_lines) 58 | 59 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 60 | if wexpect.spawn_class_name == 'SpawnSocket': 61 | p.wait() 62 | 63 | 64 | if __name__ == '__main__': 65 | unittest.main() 66 | 67 | suite = unittest.makeSuite(ReadLineTestCase,'test') 68 | -------------------------------------------------------------------------------- /tests/test_run.py: -------------------------------------------------------------------------------- 1 | import wexpect 2 | import unittest 3 | import subprocess 4 | import tempfile 5 | import sys 6 | import os 7 | import re 8 | from tests import PexpectTestCase 9 | 10 | unicode_type = str 11 | 12 | 13 | def timeout_callback(values): 14 | if values["event_count"] > 3: 15 | return 1 16 | return 0 17 | 18 | 19 | def function_events_callback(values): 20 | try: 21 | previous_echoed = values["child_result_list"][-1] 22 | # lets pick up the line with valuable 23 | previous_echoed = previous_echoed.splitlines()[-3].strip() 24 | if previous_echoed.endswith("reserved."): 25 | return "echo stage-1\r\n" 26 | if previous_echoed.endswith("stage-1"): 27 | return "echo stage-2\r\n" 28 | elif previous_echoed.endswith("stage-2"): 29 | return "echo stage-3\r\n" 30 | elif previous_echoed.endswith("stage-3"): 31 | return "exit\r\n" 32 | else: 33 | raise Exception("Unexpected output {0}".format(previous_echoed)) 34 | except IndexError: 35 | return "echo stage-1\r\n" 36 | 37 | 38 | class RunFuncTestCase(PexpectTestCase.PexpectTestCase): 39 | runfunc = staticmethod(wexpect.run) 40 | cr = '\r' 41 | empty = '' 42 | prep_subprocess_out = staticmethod(lambda x: x) 43 | 44 | def test_run_exit(self): 45 | (data, exitstatus) = self.runfunc('python exit1.py', withexitstatus=1) 46 | assert exitstatus == 1, "Exit status of 'python exit1.py' should be 1." 47 | 48 | def test_run(self): 49 | the_old_way = subprocess.Popen( 50 | args=['uname', '-m', '-n'], 51 | stdout=subprocess.PIPE 52 | ).communicate()[0].rstrip() 53 | 54 | (the_new_way, exitstatus) = self.runfunc( 55 | 'uname -m -n', withexitstatus=1) 56 | the_new_way = the_new_way.replace(self.cr, self.empty).rstrip() 57 | 58 | self.assertEqual(self.prep_subprocess_out(the_old_way).decode('utf-8'), the_new_way) 59 | self.assertEqual(exitstatus, 0) 60 | 61 | def test_run_callback(self): 62 | # TODO it seems like this test could block forever if run fails... 63 | events = {wexpect.TIMEOUT: timeout_callback} 64 | self.runfunc("cat", timeout=1, events=events) 65 | 66 | def test_run_bad_exitstatus(self): 67 | (the_new_way, exitstatus) = self.runfunc( 68 | 'ls -l /najoeufhdnzkxjd', withexitstatus=1) 69 | self.assertNotEqual(exitstatus, 0) 70 | 71 | def test_run_event_as_string(self): 72 | re_flags = re.DOTALL | re.MULTILINE 73 | events = { 74 | # second match on 'abc', echo 'def' 75 | re.compile('abc.*>', re_flags): 'echo "def"\r\n', 76 | # final match on 'def': exit 77 | re.compile('def.*>', re_flags): 'exit\r\n', 78 | # first match on 'GO:' prompt, echo 'abc' 79 | re.compile('Microsoft.*>', re_flags): 'echo "abc"\r\n' 80 | } 81 | 82 | (data, exitstatus) = wexpect.run( 83 | 'cmd', 84 | withexitstatus=True, 85 | events=events, 86 | timeout=5) 87 | assert exitstatus == 0 88 | 89 | def test_run_event_as_function(self): 90 | events = {'>': function_events_callback} 91 | 92 | (data, exitstatus) = wexpect.run( 93 | 'cmd', 94 | withexitstatus=True, 95 | events=events, 96 | timeout=10) 97 | assert exitstatus == 0 98 | 99 | # Unsupported 100 | # def test_run_event_as_method(self): 101 | # events = { 102 | # '>': RunFuncTestCase._method_events_callback 103 | # } 104 | # 105 | # (data, exitstatus) = wexpect.run( 106 | # 'cmd', 107 | # withexitstatus=True, 108 | # events=events, 109 | # timeout=10) 110 | # assert exitstatus == 0 111 | 112 | def test_run_event_typeerror(self): 113 | events = {'>': -1} 114 | with self.assertRaises(TypeError): 115 | wexpect.run('cmd', 116 | withexitstatus=True, 117 | events=events, 118 | timeout=10) 119 | 120 | def _method_events_callback(self, values): 121 | try: 122 | previous_echoed = (values["child_result_list"][-1].decode() 123 | .split("\n")[-2].strip()) 124 | if previous_echoed.endswith("foo1"): 125 | return "echo foo2\n" 126 | elif previous_echoed.endswith("foo2"): 127 | return "echo foo3\n" 128 | elif previous_echoed.endswith("foo3"): 129 | return "exit\n" 130 | else: 131 | raise Exception("Unexpected output {0!r}" 132 | .format(previous_echoed)) 133 | except IndexError: 134 | return "echo foo1\n" 135 | 136 | 137 | if __name__ == '__main__': 138 | unittest.main() 139 | -------------------------------------------------------------------------------- /tests/test_timeout_pattern.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import unittest 3 | import subprocess 4 | import time 5 | import signal 6 | import sys 7 | import os 8 | 9 | import wexpect 10 | from tests import PexpectTestCase 11 | 12 | @unittest.skipIf(wexpect.spawn_class_name == 'legacy_wexpect', "legacy unsupported") 13 | class Exp_TimeoutTestCase(PexpectTestCase.PexpectTestCase): 14 | def test_matches_exp_timeout (self): 15 | '''This tests that we can raise and catch TIMEOUT. 16 | ''' 17 | try: 18 | raise wexpect.TIMEOUT("TIMEOUT match test") 19 | except wexpect.TIMEOUT: 20 | pass 21 | #print "Correctly caught TIMEOUT when raising TIMEOUT." 22 | else: 23 | self.fail('TIMEOUT not caught by an except TIMEOUT clause.') 24 | 25 | def test_pattern_printout (self): 26 | '''Verify that a TIMEOUT returns the proper patterns it is trying to match against. 27 | Make sure it is returning the pattern from the correct call.''' 28 | try: 29 | p = wexpect.spawn('cat') 30 | p.sendline('Hello') 31 | p.expect('Hello') 32 | p.expect('Goodbye',timeout=5) 33 | except wexpect.TIMEOUT: 34 | assert p.match_index == None 35 | else: 36 | self.fail("Did not generate a TIMEOUT exception.") 37 | 38 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 39 | if wexpect.spawn_class_name == 'SpawnSocket': 40 | p.terminate() 41 | 42 | def test_exp_timeout_notThrown (self): 43 | '''Verify that a TIMEOUT is not thrown when we match what we expect.''' 44 | try: 45 | p = wexpect.spawn('cat') 46 | p.sendline('Hello') 47 | p.expect('Hello') 48 | except wexpect.TIMEOUT: 49 | self.fail("TIMEOUT caught when it shouldn't be raised because we match the proper pattern.") 50 | 51 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 52 | if wexpect.spawn_class_name == 'SpawnSocket': 53 | p.terminate() 54 | 55 | def test_stacktraceMunging (self): 56 | '''Verify that the stack trace returned with a TIMEOUT instance does not contain references to wexpect.''' 57 | try: 58 | p = wexpect.spawn('cat') 59 | p.sendline('Hello') 60 | p.expect('Goodbye',timeout=5) 61 | except wexpect.TIMEOUT: 62 | err = sys.exc_info()[1] 63 | if err.get_trace().count("wexpect/__init__.py") != 0: 64 | self.fail("The TIMEOUT get_trace() referenced wexpect.py. " 65 | "It should only reference the caller.\n" + err.get_trace()) 66 | 67 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 68 | if wexpect.spawn_class_name == 'SpawnSocket': 69 | p.terminate() 70 | 71 | def test_correctStackTrace (self): 72 | '''Verify that the stack trace returned with a TIMEOUT instance correctly handles function calls.''' 73 | def nestedFunction (spawnInstance): 74 | spawnInstance.expect("junk", timeout=3) 75 | 76 | try: 77 | p = wexpect.spawn('cat') 78 | p.sendline('Hello') 79 | nestedFunction(p) 80 | except wexpect.TIMEOUT: 81 | err = sys.exc_info()[1] 82 | if err.get_trace().count("nestedFunction") == 0: 83 | self.fail("The TIMEOUT get_trace() did not show the call " 84 | "to the nestedFunction function.\n" + str(err) + "\n" 85 | + err.get_trace()) 86 | 87 | # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. 88 | if wexpect.spawn_class_name == 'SpawnSocket': 89 | p.terminate() 90 | 91 | if __name__ == '__main__': 92 | unittest.main() 93 | 94 | suite = unittest.makeSuite(Exp_TimeoutTestCase,'test') 95 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def no_coverage_env(): 4 | "Return a copy of os.environ that won't trigger coverage measurement." 5 | env = os.environ.copy() 6 | env.pop('COV_CORE_SOURCE', None) 7 | return env -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox.ini 2 | # Run tests of wexpect in multiple configuration. 3 | 4 | [tox] 5 | # The following configuration will run automatically. 6 | envlist = py{37}-{legacy_wexpect,spawn_pipe,spawn_socket},installed,pyinstaller 7 | 8 | 9 | [testenv] 10 | description = Unit tests 11 | 12 | # Set environment variables to select the proper configuration for each envirnment. 13 | setenv = 14 | spawn_pipe: WEXPECT_SPAWN_CLASS=SpawnPipe 15 | legacy_wexpect: WEXPECT_SPAWN_CLASS=legacy_wexpect 16 | spawn_socket: WEXPECT_SPAWN_CLASS=SpawnSocket 17 | 18 | commands = 19 | # install the dependencies: 20 | pip install .[test] 21 | 22 | # Run the test itself 23 | coverage run --parallel-mode -m unittest 24 | 25 | # Combine the parallel results 26 | coverage combine 27 | 28 | # Dump coverage summary to console 29 | coverage report --omit=tests/*,site-packages/* 30 | 31 | # Convert coverage report to standard xml formula the filename includes the tox environment name 32 | # https://tox.readthedocs.io/en/latest/config.html#environment-variable-substitutions 33 | coverage xml --omit=tests/*,site-packages -o {env:TOX_ENV_NAME}_coverage.xml 34 | 35 | 36 | [testenv:installed] 37 | # normal tests test the cloned files. This testenv tests the installation itself, with the default 38 | # spawn class. 39 | description = Unit tests installed, and default spawn class. 40 | 41 | changedir = test_01_installed 42 | 43 | whitelist_externals = 44 | cp 45 | 46 | commands = 47 | # copy all testcase into working dir 48 | cp -r ../tests tests 49 | 50 | 51 | # Run the test itself. Running all tests is not needed, because it just test the installation, 52 | # not functions. 53 | python -m unittest tests.test_misc 54 | 55 | 56 | [testenv:pyinstaller] 57 | # Test if wexpect working with pyinstaller. See #12 for more details. 58 | description = Unit tests pyinstaller 59 | 60 | whitelist_externals = 61 | pyinstaller 62 | pyinstaller_test 63 | 64 | setenv = 65 | WEXPECT_SPAWN_CLASS=SpawnPipe 66 | 67 | commands = 68 | # install the dependencies: 69 | pip install .[test] 70 | 71 | # create wexpect executable for console-reader. 72 | pyinstaller wexpect.spec 73 | 74 | # create test executable, to thest the console-reader 75 | pyinstaller tests/pyinstaller_test.py 76 | 77 | # run test: 78 | dist\pyinstaller_test\pyinstaller_test.exe 79 | -------------------------------------------------------------------------------- /wexpect.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['wexpect\\__main__.py'], 7 | pathex=[], 8 | binaries=[], 9 | datas=[], 10 | hiddenimports=[], 11 | hookspath=[], 12 | runtime_hooks=[], 13 | excludes=[], 14 | win_no_prefer_redirects=False, 15 | win_private_assemblies=False, 16 | cipher=block_cipher, 17 | noarchive=False) 18 | pyz = PYZ(a.pure, a.zipped_data, 19 | cipher=block_cipher) 20 | exe = EXE(pyz, 21 | a.scripts, 22 | [], 23 | exclude_binaries=True, 24 | name='wexpect', 25 | debug=False, 26 | bootloader_ignore_signals=False, 27 | strip=False, 28 | upx=True, 29 | console=True ) 30 | coll = COLLECT(exe, 31 | a.binaries, 32 | a.zipfiles, 33 | a.datas, 34 | strip=False, 35 | upx=True, 36 | upx_exclude=[], 37 | name='wexpect') 38 | -------------------------------------------------------------------------------- /wexpect/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | 3 | import os 4 | import pkg_resources 5 | 6 | try: 7 | spawn_class_name = os.environ['WEXPECT_SPAWN_CLASS'] 8 | except KeyError: 9 | spawn_class_name = 'SpawnPipe' 10 | 11 | if spawn_class_name == 'legacy_wexpect': 12 | from .legacy_wexpect import ExceptionPexpect 13 | from .legacy_wexpect import EOF 14 | from .legacy_wexpect import TIMEOUT 15 | from .legacy_wexpect import spawn 16 | from .legacy_wexpect import run 17 | from .legacy_wexpect import split_command_line 18 | from .legacy_wexpect import join_args 19 | from .legacy_wexpect import ConsoleReader 20 | from .legacy_wexpect import __version__ 21 | from .legacy_wexpect import searcher_string 22 | from .legacy_wexpect import searcher_re 23 | 24 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run', 'split_command_line', 25 | '__version__', 'ConsoleReader', 'join_args', 'searcher_string', 'searcher_re', 26 | 'spawn_class_name'] 27 | 28 | else: 29 | 30 | from .wexpect_util import split_command_line 31 | from .wexpect_util import join_args 32 | from .wexpect_util import ExceptionPexpect 33 | from .wexpect_util import EOF 34 | from .wexpect_util import TIMEOUT 35 | 36 | from .console_reader import ConsoleReaderSocket 37 | from .console_reader import ConsoleReaderPipe 38 | 39 | from .host import SpawnSocket 40 | from .host import SpawnPipe 41 | from .host import run 42 | from .host import searcher_string 43 | from .host import searcher_re 44 | 45 | try: 46 | spawn = globals()[spawn_class_name] 47 | except KeyError: 48 | print(f'Error: no spawn class: {spawn_class_name}') 49 | raise 50 | 51 | # The version is handled by the package: pbr, which derives the version from the git tags. 52 | try: 53 | __version__ = pkg_resources.require("wexpect")[0].version 54 | except Exception: # pragma: no cover 55 | __version__ = '0.0.1.unkowndev0' 56 | 57 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'ConsoleReaderSocket', 'ConsoleReaderPipe', 58 | 'spawn', 'SpawnSocket', 'SpawnPipe', 'run', '__version__', 'spawn_class_name'] 59 | -------------------------------------------------------------------------------- /wexpect/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import logging 4 | import traceback 5 | 6 | import wexpect.console_reader as console_reader 7 | import wexpect.wexpect_util as wexpect_util 8 | 9 | 10 | logger = logging.getLogger('wexpect') 11 | logger.info('Hello') 12 | 13 | def main(): 14 | try: 15 | parser = argparse.ArgumentParser(description='Wexpect: executable automation for Windows.') 16 | 17 | parser.add_argument('--console_reader_class', type=str, 18 | help='Class name of the console reader class.') 19 | 20 | parser.add_argument('command', type=str, nargs='+', help='Command to be run with its arguments') 21 | parser.add_argument('--host_pid', type=int, help='Host process process ID') 22 | parser.add_argument('--codepage', type=str, help='Codepage') 23 | parser.add_argument('--window_size_x', type=int, help='Width of the console window', default=80) 24 | parser.add_argument('--window_size_y', type=int, help='Height of the console window', default=25) 25 | parser.add_argument('--buffer_size_x', type=int, help='Width of the console buffer', default=80) 26 | parser.add_argument('--buffer_size_y', type=int, help='Height of the console buffer', 27 | default=16000) 28 | parser.add_argument('--local_echo', type=str, help='Echo sent characters', default=True) 29 | parser.add_argument('--interact', type=str, help='Show console window', default=False) 30 | parser.add_argument('--port', type=int, help= 31 | "If the console reader class is SpawnSocket, this option specifies the " 32 | "socket's port.", default=False) 33 | 34 | try: 35 | args = parser.parse_args() 36 | except SystemExit: # pragma: no cover 37 | logger.error('Unexpected exception.') 38 | logger.info(traceback.format_exc()) 39 | raise 40 | 41 | logger.info(f'Starter arguments: {args}') 42 | 43 | if args.console_reader_class == 'ConsoleReaderSocket': 44 | conole_reader_class = console_reader.ConsoleReaderSocket 45 | elif args.console_reader_class == 'ConsoleReaderPipe': 46 | conole_reader_class = console_reader.ConsoleReaderPipe 47 | 48 | command = wexpect_util.join_args(args.command) 49 | 50 | cons = conole_reader_class( 51 | path=command, host_pid=args.host_pid, codepage=args.codepage, port=args.port, 52 | window_size_x=args.window_size_x, window_size_y=args.window_size_y, 53 | buffer_size_x=args.buffer_size_x, buffer_size_y=args.buffer_size_y, 54 | local_echo=wexpect_util.str2bool(args.local_echo), interact=wexpect_util.str2bool(args.interact)) 55 | 56 | logger.info(f'Exiting with status: {cons.child_exitstatus}') 57 | sys.exit(cons.child_exitstatus) 58 | 59 | except Exception as e: # pragma: no cover 60 | logger.error('Unexpected exception.') 61 | logger.info(traceback.format_exc()) 62 | raise 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /wexpect/console_reader.py: -------------------------------------------------------------------------------- 1 | """Wexpect is a Windows variant of pexpect https://pexpect.readthedocs.io. 2 | 3 | Wexpect is a Python module for spawning child applications and controlling 4 | them automatically. 5 | 6 | console_reader Implements a virtual terminal, and starts the child program. 7 | The main wexpect.spawn class connect to this class to reach the child's terminal. 8 | """ 9 | 10 | import time 11 | import logging 12 | import os 13 | import traceback 14 | import psutil 15 | from io import StringIO 16 | 17 | import ctypes 18 | from ctypes import windll 19 | import win32console 20 | import win32process 21 | import win32con 22 | import win32file 23 | import win32gui 24 | import win32pipe 25 | import socket 26 | 27 | from .wexpect_util import init_logger 28 | from .wexpect_util import EOF_CHAR 29 | from .wexpect_util import SIGNAL_CHARS 30 | from .wexpect_util import TIMEOUT 31 | 32 | # 33 | # System-wide constants 34 | # 35 | screenbufferfillchar = '\4' 36 | maxconsoleY = 8000 37 | default_port = 4321 38 | 39 | # 40 | # Create logger: We write logs only to file. Printing out logs are dangerous, because of the deep 41 | # console manipulation. 42 | # 43 | logger = logging.getLogger('wexpect') 44 | 45 | 46 | class ConsoleReaderBase: 47 | """Consol class (aka. client-side python class) for the child. 48 | 49 | This class initialize the console starts the child in it and reads the console periodically. 50 | """ 51 | 52 | def __init__(self, path, host_pid, codepage=None, window_size_x=80, window_size_y=25, 53 | buffer_size_x=80, buffer_size_y=16000, local_echo=True, interact=False, **kwargs): 54 | """Initialize the console starts the child in it and reads the console periodically. 55 | 56 | Args: 57 | path (str): Child's executable with arguments. 58 | parent_pid (int): Parent (aka. host) process process-ID 59 | codepage (:obj:, optional): Output console code page. 60 | """ 61 | self.lastRead = 0 62 | self.__bufferY = 0 63 | self.lastReadData = "" 64 | self.totalRead = 0 65 | self.__buffer = StringIO() 66 | self.__currentReadCo = win32console.PyCOORDType(0, 0) 67 | self.pipe = None 68 | self.connection = None 69 | self.consin = None 70 | self.consout = None 71 | self.local_echo = local_echo 72 | self.console_pid = os.getpid() 73 | self.host_pid = host_pid 74 | self.host_process = psutil.Process(host_pid) 75 | self.child_process = None 76 | self.child_pid = None 77 | self.enable_signal_chars = True 78 | self.timeout = 30 79 | self.child_exitstatus = None 80 | 81 | logger.info(f'ConsoleReader started. location {os.path.abspath(__file__)}') 82 | 83 | if codepage is None: 84 | codepage = windll.kernel32.GetACP() 85 | 86 | try: 87 | logger.info("Setting console output code page to %s" % codepage) 88 | win32console.SetConsoleOutputCP(codepage) 89 | logger.info( 90 | "Console output code page: %s" % ctypes.windll.kernel32.GetConsoleOutputCP()) 91 | except Exception as e: # pragma: no cover 92 | # I hope this code is unreachable... 93 | logger.error(e) 94 | 95 | try: 96 | self.create_connection(**kwargs) 97 | logger.info('Spawning %s' % path) 98 | try: 99 | self.initConsole() 100 | si = win32process.GetStartupInfo() 101 | self.__childProcess, _, self.child_pid, self.child_tid = win32process.CreateProcess( 102 | None, path, None, None, False, 0, None, None, si) 103 | self.child_process = psutil.Process(self.child_pid) 104 | 105 | logger.info(f'Child pid: {self.child_pid} Console pid: {self.console_pid}') 106 | 107 | except Exception: # pragma: no cover 108 | # I hope this code is unreachable... 109 | logger.error(traceback.format_exc()) 110 | return 111 | 112 | if interact: 113 | self.interact() 114 | self.interact() 115 | 116 | self.read_loop() 117 | except Exception: # pragma: no cover 118 | # I hope this code is unreachable... 119 | logger.error(traceback.format_exc()) 120 | finally: 121 | try: 122 | self.terminate_child() 123 | time.sleep(.01) 124 | self.send_to_host(self.readConsoleToCursor()) 125 | self.sendeof() 126 | time.sleep(.1) 127 | self.close_connection() 128 | logger.info('Console finished.') 129 | except Exception: # pragma: no cover 130 | # I hope this code is unreachable... 131 | logger.error(traceback.format_exc()) 132 | 133 | def read_loop(self): 134 | 135 | while True: 136 | if not self.isalive(self.host_process): 137 | logger.info('Host process has been died.') 138 | return 139 | 140 | self.child_exitstatus = win32process.GetExitCodeProcess(self.__childProcess) 141 | if self.child_exitstatus != win32con.STILL_ACTIVE: 142 | logger.info(f'Child finished with code: {self.child_exitstatus}') 143 | return 144 | 145 | consinfo = self.consout.GetConsoleScreenBufferInfo() 146 | cursorPos = consinfo['CursorPosition'] 147 | 148 | if cursorPos.Y > maxconsoleY: 149 | '''If the console output becomes long, we suspend the child, read all output then 150 | clear the console before we resume the child. 151 | ''' 152 | logger.info('cursorPos %s' % cursorPos) 153 | self.suspend_child() 154 | time.sleep(.2) 155 | self.send_to_host(self.readConsoleToCursor()) 156 | self.refresh_console() 157 | self.resume_child() 158 | else: 159 | self.send_to_host(self.readConsoleToCursor()) 160 | 161 | s = self.get_from_host() 162 | if s: 163 | logger.debug(f'get_from_host: {s}') 164 | else: 165 | logger.spam(f'get_from_host: {s}') 166 | if self.enable_signal_chars: 167 | for sig, char in SIGNAL_CHARS.items(): 168 | if char in s: 169 | self.child_process.send_signal(sig) 170 | s = s.decode() 171 | self.write(s) 172 | 173 | 174 | time.sleep(.02) 175 | 176 | def suspend_child(self): 177 | """Pauses the main thread of the child process.""" 178 | handle = windll.kernel32.OpenThread(win32con.THREAD_SUSPEND_RESUME, 0, self.child_tid) 179 | win32process.SuspendThread(handle) 180 | 181 | def resume_child(self): 182 | """Un-pauses the main thread of the child process.""" 183 | handle = windll.kernel32.OpenThread(win32con.THREAD_SUSPEND_RESUME, 0, self.child_tid) 184 | win32process.ResumeThread(handle) 185 | 186 | def refresh_console(self): 187 | """Clears the console after pausing the child and 188 | reading all the data currently on the console.""" 189 | 190 | orig = win32console.PyCOORDType(0, 0) 191 | self.consout.SetConsoleCursorPosition(orig) 192 | self.__currentReadCo.X = 0 193 | self.__currentReadCo.Y = 0 194 | writelen = self.__consSize.X * self.__consSize.Y 195 | # Use NUL as fill char because it displays as whitespace 196 | # (if we interact() with the child) 197 | self.consout.FillConsoleOutputCharacter(screenbufferfillchar, writelen, orig) 198 | 199 | self.__bufferY = 0 200 | self.__buffer.truncate(0) 201 | 202 | def terminate_child(self): 203 | try: 204 | if self.child_process: 205 | self.child_process.kill() 206 | except psutil.NoSuchProcess: 207 | logger.info('The process has already died.') 208 | return 209 | 210 | def isalive(self, process): 211 | """True if the child is still alive, false otherwise""" 212 | try: 213 | self.exitstatus = process.wait(timeout=0) 214 | return False 215 | except psutil.TimeoutExpired: 216 | return True 217 | 218 | def write(self, s): 219 | """Writes input into the child consoles input buffer.""" 220 | 221 | if len(s) == 0: 222 | return 0 223 | if s[-1] == '\n': 224 | s = s[:-1] 225 | records = [self.createKeyEvent(c) for c in str(s)] 226 | if not self.consout: 227 | return "" 228 | 229 | # Store the current cursor position to hide characters in local echo disabled mode 230 | # (workaround). 231 | consinfo = self.consout.GetConsoleScreenBufferInfo() 232 | startCo = consinfo['CursorPosition'] 233 | 234 | # Send the string to console input 235 | wrote = self.consin.WriteConsoleInput(records) 236 | 237 | # Wait until all input has been recorded by the console. 238 | ts = time.time() 239 | while self.consin.PeekConsoleInput(8) != (): 240 | if time.time() > ts + len(s) * .1 + .5: 241 | break 242 | time.sleep(.05) 243 | 244 | # Hide characters in local echo disabled mode (workaround). 245 | if not self.local_echo: 246 | self.consout.FillConsoleOutputCharacter(screenbufferfillchar, len(s), startCo) 247 | 248 | return wrote 249 | 250 | def createKeyEvent(self, char): 251 | """Creates a single key record corrosponding to 252 | the ascii character char.""" 253 | 254 | evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT) 255 | evt.KeyDown = True 256 | evt.Char = char 257 | evt.RepeatCount = 1 258 | return evt 259 | 260 | def initConsole(self, consout=None, window_size_x=80, window_size_y=25, buffer_size_x=80, 261 | buffer_size_y=16000): 262 | if not consout: 263 | consout = self.getConsoleOut() 264 | 265 | self.consin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE) 266 | 267 | rect = win32console.PySMALL_RECTType(0, 0, window_size_x - 1, window_size_y - 1) 268 | consout.SetConsoleWindowInfo(True, rect) 269 | size = win32console.PyCOORDType(buffer_size_x, buffer_size_y) 270 | consout.SetConsoleScreenBufferSize(size) 271 | pos = win32console.PyCOORDType(0, 0) 272 | # Use NUL as fill char because it displays as whitespace 273 | # (if we interact() with the child) 274 | consout.FillConsoleOutputCharacter(screenbufferfillchar, size.X * size.Y, pos) 275 | 276 | consinfo = consout.GetConsoleScreenBufferInfo() 277 | self.__consSize = consinfo['Size'] 278 | logger.info('self.__consSize: ' + str(self.__consSize)) 279 | self.startCursorPos = consinfo['CursorPosition'] 280 | 281 | def parseData(self, s): 282 | """Ensures that special characters are interpretted as 283 | newlines or blanks, depending on if there written over 284 | characters or screen-buffer-fill characters.""" 285 | 286 | strlist = [] 287 | for i, c in enumerate(s): 288 | if c == screenbufferfillchar: 289 | if (self.totalRead - self.lastRead + i + 1) % self.__consSize.X == 0: 290 | strlist.append('\r\n') 291 | else: 292 | strlist.append(c) 293 | 294 | s = ''.join(strlist) 295 | return s 296 | 297 | def getConsoleOut(self): 298 | consfile = win32file.CreateFile( 299 | 'CONOUT$', 300 | win32con.GENERIC_READ | win32con.GENERIC_WRITE, 301 | win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE, 302 | None, 303 | win32con.OPEN_EXISTING, 304 | 0, 305 | 0) 306 | 307 | self.consout = win32console.PyConsoleScreenBufferType(consfile) 308 | return self.consout 309 | 310 | def getCoord(self, offset): 311 | """Converts an offset to a point represented as a tuple.""" 312 | 313 | x = offset % self.__consSize.X 314 | y = offset // self.__consSize.X 315 | return win32console.PyCOORDType(x, y) 316 | 317 | def getOffset(self, coord): 318 | """Converts a tuple-point to an offset.""" 319 | 320 | return coord.X + coord.Y * self.__consSize.X 321 | 322 | def readConsole(self, startCo, endCo): 323 | """Reads the console area from startCo to endCo and returns it 324 | as a string.""" 325 | 326 | if startCo is None: 327 | startCo = self.startCursorPos 328 | startCo.Y = startCo.Y 329 | 330 | if endCo is None: 331 | consinfo = self.consout.GetConsoleScreenBufferInfo() 332 | endCo = consinfo['CursorPosition'] 333 | endCo = self.getCoord(0 + self.getOffset(endCo)) 334 | 335 | buff = [] 336 | self.lastRead = 0 337 | 338 | while True: 339 | startOff = self.getOffset(startCo) 340 | endOff = self.getOffset(endCo) 341 | readlen = endOff - startOff 342 | 343 | if readlen <= 0: 344 | break 345 | 346 | if readlen > 4000: 347 | readlen = 4000 348 | endPoint = self.getCoord(startOff + readlen) 349 | 350 | s = self.consout.ReadConsoleOutputCharacter(readlen, startCo) 351 | self.lastRead += len(s) 352 | self.totalRead += len(s) 353 | buff.append(s) 354 | 355 | startCo = endPoint 356 | 357 | return ''.join(buff) 358 | 359 | def readConsoleToCursor(self): 360 | """Reads from the current read position to the current cursor 361 | position and inserts the string into self.__buffer.""" 362 | 363 | if not self.consout: 364 | return "" 365 | 366 | consinfo = self.consout.GetConsoleScreenBufferInfo() 367 | cursorPos = consinfo['CursorPosition'] 368 | 369 | logger.spam('cursor: %r, current: %r' % (cursorPos, self.__currentReadCo)) 370 | 371 | isSameX = cursorPos.X == self.__currentReadCo.X 372 | isSameY = cursorPos.Y == self.__currentReadCo.Y 373 | isSamePos = isSameX and isSameY 374 | 375 | logger.spam('isSameY: %r' % isSameY) 376 | logger.spam('isSamePos: %r' % isSamePos) 377 | 378 | if isSameY or not self.lastReadData.endswith('\r\n'): 379 | # Read the current slice again 380 | self.totalRead -= self.lastRead 381 | self.__currentReadCo.X = 0 382 | self.__currentReadCo.Y = self.__bufferY 383 | 384 | logger.spam('cursor: %r, current: %r' % (cursorPos, self.__currentReadCo)) 385 | 386 | raw = self.readConsole(self.__currentReadCo, cursorPos) 387 | rawlist = [] 388 | while raw: 389 | rawlist.append(raw[:self.__consSize.X]) 390 | raw = raw[self.__consSize.X:] 391 | raw = ''.join(rawlist) 392 | s = self.parseData(raw) 393 | for i, line in enumerate(reversed(rawlist)): 394 | if line.endswith(screenbufferfillchar): 395 | # Record the Y offset where the most recent line break was detected 396 | self.__bufferY += len(rawlist) - i 397 | break 398 | 399 | logger.spam('lastReadData: %r' % self.lastReadData) 400 | if s: 401 | logger.debug('Read: %r' % s) 402 | else: 403 | logger.spam('Read: %r' % s) 404 | 405 | if isSamePos and self.lastReadData == s: 406 | logger.spam('isSamePos and self.lastReadData == s') 407 | s = '' 408 | 409 | if s: 410 | lastReadData = self.lastReadData 411 | pos = self.getOffset(self.__currentReadCo) 412 | self.lastReadData = s 413 | if isSameY or not lastReadData.endswith('\r\n'): 414 | # Detect changed lines 415 | self.__buffer.seek(pos) 416 | buf = self.__buffer.read() 417 | if raw.startswith(buf): 418 | # Line has grown 419 | rawslice = raw[len(buf):] 420 | # Update last read bytes so line breaks can be detected in parseData 421 | lastRead = self.lastRead 422 | self.lastRead = len(rawslice) 423 | s = self.parseData(rawslice) 424 | self.lastRead = lastRead 425 | else: 426 | # Cursor has been repositioned 427 | s = '\r' + s 428 | self.__buffer.seek(pos) 429 | self.__buffer.truncate() 430 | self.__buffer.write(raw) 431 | 432 | self.__currentReadCo.X = cursorPos.X 433 | self.__currentReadCo.Y = cursorPos.Y 434 | 435 | return s 436 | 437 | def interact(self): 438 | """Displays the child console for interaction.""" 439 | 440 | logger.debug('Start interact window') 441 | win32gui.ShowWindow(win32console.GetConsoleWindow(), win32con.SW_SHOW) 442 | 443 | def sendeof(self): 444 | """This sends an EOF to the host. This sends a character which inform the host that child 445 | has been finished, and all of it's output has been send to host. 446 | """ 447 | 448 | self.send_to_host(EOF_CHAR) 449 | 450 | 451 | class ConsoleReaderSocket(ConsoleReaderBase): 452 | 453 | def create_connection(self, **kwargs): 454 | try: 455 | self.port = kwargs['port'] 456 | # Create a TCP/IP socket 457 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 458 | server_address = ('localhost', self.port) 459 | self.sock.bind(server_address) 460 | logger.info(f'Socket started at port: {self.port}') 461 | 462 | # Listen for incoming connections 463 | self.sock.settimeout(5) 464 | self.sock.listen(1) 465 | self.connection, client_address = self.sock.accept() 466 | self.connection.settimeout(.01) 467 | logger.info(f'Client connected: {client_address}') 468 | except Exception as e: # pragma: no cover 469 | # I hope this code is unreachable. 470 | logger.error(f"Port: {self.port} {e}") 471 | raise 472 | 473 | def close_connection(self): 474 | if self.connection: 475 | self.connection.shutdown(socket.SHUT_RDWR) 476 | self.connection.close() 477 | self.connection = None 478 | 479 | def send_to_host(self, msg): 480 | # convert to bytes 481 | if isinstance(msg, str): 482 | msg = str.encode(msg) 483 | if msg: 484 | logger.debug(f'Sending msg: {msg}') 485 | else: 486 | logger.spam(f'Sending msg: {msg}') 487 | self.connection.sendall(msg) 488 | 489 | def get_from_host(self): 490 | try: 491 | msg = self.connection.recv(4096) 492 | except socket.timeout as e: 493 | err = e.args[0] 494 | # this next if/else is a bit redundant, but illustrates how the 495 | # timeout exception is setup 496 | if err == 'timed out': 497 | logger.debug('recv timed out, retry later') 498 | return b'' 499 | else: 500 | raise 501 | else: 502 | if len(msg) == 0: 503 | raise Exception('orderly shutdown on server end') 504 | else: 505 | # got a message do something :) 506 | return msg 507 | 508 | 509 | class ConsoleReaderPipe(ConsoleReaderBase): 510 | def create_connection(self, timeout=-1, **kwargs): 511 | if timeout == -1: 512 | timeout = self.timeout 513 | if timeout is None: 514 | end_time = float('inf') 515 | else: 516 | end_time = time.time() + timeout 517 | 518 | pipe_name = 'wexpect_{}'.format(self.console_pid) 519 | pipe_full_path = r'\\.\pipe\{}'.format(pipe_name) 520 | logger.info('Start pipe server: %s', pipe_full_path) 521 | self.pipe = win32pipe.CreateNamedPipe( 522 | pipe_full_path, 523 | win32pipe.PIPE_ACCESS_DUPLEX, 524 | win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_NOWAIT, 525 | 1, 65536, 65536, 10000, None) 526 | logger.info("waiting for client") 527 | while True: 528 | if end_time < time.time(): 529 | raise TIMEOUT('Connect to child has been timed out.') 530 | try: 531 | win32pipe.ConnectNamedPipe(self.pipe, None) 532 | break 533 | except Exception as e: 534 | logger.debug(e) 535 | time.sleep(0.2) 536 | logger.info('got client') 537 | 538 | def close_connection(self): 539 | if self.pipe: 540 | win32file.CloseHandle(self.pipe) 541 | 542 | def send_to_host(self, msg): 543 | # convert to bytes 544 | if isinstance(msg, str): 545 | msg = str.encode(msg) 546 | if msg: 547 | logger.debug(f'Sending msg: {msg}') 548 | else: 549 | logger.spam(f'Sending msg: {msg}') 550 | win32file.WriteFile(self.pipe, msg) 551 | 552 | def get_from_host(self): 553 | data, avail, bytes_left = win32pipe.PeekNamedPipe(self.pipe, 4096) 554 | logger.spam(f'data: {data} avail:{avail} bytes_left{bytes_left}') 555 | if avail > 0: 556 | resp = win32file.ReadFile(self.pipe, 4096) 557 | ret = resp[1] 558 | return ret 559 | else: 560 | return b'' 561 | -------------------------------------------------------------------------------- /wexpect/wexpect_util.py: -------------------------------------------------------------------------------- 1 | """Wexpect is a Windows variant of pexpect https://pexpect.readthedocs.io. 2 | 3 | Wexpect is a Python module for spawning child applications and controlling 4 | them automatically. 5 | 6 | wexpect util contains small functions, and classes, which are used in multiple classes. 7 | The command line argument parsers, and the Exceptions placed here. 8 | 9 | """ 10 | 11 | import re 12 | import traceback 13 | import sys 14 | import os 15 | import logging 16 | import signal 17 | 18 | # platform does not define VEOF so assume CTRL-D 19 | EOF_CHAR = b'\x04' 20 | 21 | SIGNAL_CHARS = { 22 | signal.SIGTERM: b'\x011', # Device control 1 23 | signal.SIGINT: b'\x012', # Device control 2 24 | } 25 | 26 | SPAM = 5 27 | logging.addLevelName(SPAM, "SPAM") 28 | 29 | 30 | def str2bool(v): 31 | if isinstance(v, bool): 32 | return v 33 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 34 | return True 35 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 36 | return False 37 | else: # pragma: no cover 38 | raise argparse.ArgumentTypeError('Boolean value expected.') 39 | 40 | 41 | def spam(self, message, *args, **kws): # pragma: no cover 42 | '''Very verbose debug dunction. 43 | ''' 44 | if self.isEnabledFor(SPAM): 45 | # Yes, logger takes its '*args' as 'args'. 46 | self._log(SPAM, message, args, **kws) 47 | 48 | 49 | logging.Logger.spam = spam 50 | 51 | 52 | def init_logger(logger=None): # pragma: no cover 53 | '''Initializes the logger. I wont measure coverage for this debug method. 54 | ''' 55 | if logger is None: 56 | logger = logging.getLogger('wexpect') 57 | try: 58 | logger_level = os.environ['WEXPECT_LOGGER_LEVEL'] 59 | try: 60 | logger_filename = os.environ['WEXPECT_LOGGER_FILENAME'] 61 | except KeyError: 62 | pid = os.getpid() 63 | logger_filename = f'./.wlog/wexpect_{pid}' 64 | logger.setLevel(logger_level) 65 | logger_filename = f'{logger_filename}.log' 66 | os.makedirs(os.path.dirname(logger_filename), exist_ok=True) 67 | fh = logging.FileHandler(logger_filename, 'a', 'utf-8') 68 | formatter = logging.Formatter( 69 | '%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s') 70 | fh.setFormatter(formatter) 71 | logger.addHandler(fh) 72 | except KeyError: 73 | logger.setLevel(logging.ERROR) 74 | 75 | 76 | def split_command_line(command_line, escape_char='^'): 77 | """This splits a command line into a list of arguments. It splits arguments 78 | on spaces, but handles embedded quotes, doublequotes, and escaped 79 | characters. It's impossible to do this with a regular expression, so I 80 | wrote a little state machine to parse the command line. """ 81 | 82 | arg_list = [] 83 | arg = '' 84 | 85 | # Constants to name the states we can be in. 86 | state_basic = 0 87 | state_esc = 1 88 | state_singlequote = 2 89 | state_doublequote = 3 90 | state_whitespace = 4 # The state of consuming whitespace between commands. 91 | state = state_basic 92 | 93 | for c in command_line: 94 | if state == state_basic or state == state_whitespace: 95 | if c == escape_char: # Escape the next character 96 | state = state_esc 97 | elif c == r"'": # Handle single quote 98 | state = state_singlequote 99 | elif c == r'"': # Handle double quote 100 | state = state_doublequote 101 | elif c.isspace(): 102 | # Add arg to arg_list if we aren't in the middle of whitespace. 103 | if state == state_whitespace: 104 | None # Do nothing. 105 | else: 106 | arg_list.append(arg) 107 | arg = '' 108 | state = state_whitespace 109 | else: 110 | arg = arg + c 111 | state = state_basic 112 | elif state == state_esc: 113 | arg = arg + c 114 | state = state_basic 115 | elif state == state_singlequote: 116 | if c == r"'": 117 | state = state_basic 118 | else: 119 | arg = arg + c 120 | elif state == state_doublequote: 121 | if c == r'"': 122 | state = state_basic 123 | else: 124 | arg = arg + c 125 | 126 | if arg != '': 127 | arg_list.append(arg) 128 | return arg_list 129 | 130 | 131 | def join_args(args): 132 | """Joins arguments a command line. It quotes all arguments that contain 133 | spaces or any of the characters ^!$%&()[]{}=;'+,`~""" 134 | commandline = [] 135 | for arg in args: 136 | if re.search('[\\^!$%&()[\\]{}=;\'+,`~\\s]', arg): 137 | arg = '"%s"' % arg 138 | commandline.append(arg) 139 | return ' '.join(commandline) 140 | 141 | 142 | class ExceptionPexpect(Exception): 143 | """Base class for all exceptions raised by this module. 144 | """ 145 | 146 | def __init__(self, value): 147 | 148 | self.value = value 149 | 150 | def __str__(self): 151 | 152 | return str(self.value) 153 | 154 | def get_trace(self): 155 | """This returns an abbreviated stack trace with lines that only concern 156 | the caller. In other words, the stack trace inside the Wexpect module 157 | is not included. """ 158 | 159 | tblist = traceback.extract_tb(sys.exc_info()[2]) 160 | tblist = [item for item in tblist if self.__filter_not_wexpect(item)] 161 | tblist = traceback.format_list(tblist) 162 | return ''.join(tblist) 163 | 164 | def __filter_not_wexpect(self, trace_list_item): 165 | """This returns True if list item 0 the string 'wexpect.py' in it. """ 166 | 167 | if trace_list_item[0].find('host.py') == -1: 168 | return True 169 | else: 170 | return False 171 | 172 | 173 | class EOF(ExceptionPexpect): 174 | """Raised when EOF is read from a child. This usually means the child has exited. 175 | The user can wait to EOF, which means he waits the end of the execution of the child process.""" 176 | 177 | 178 | class TIMEOUT(ExceptionPexpect): 179 | """Raised when a read time exceeds the timeout. """ 180 | 181 | init_logger() 182 | --------------------------------------------------------------------------------