diff --git a/.gitignore b/.gitignore index d7ac02a5..bc064d5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,76 @@ -# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) -[Bb]in/ -[Oo]bj/ +# Files relevant to ScriptCS project -# mstest test results -TestResults - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. +artifacts +/scriptcs_packages # Diff files *.orig +# These are extra items from the original ignore file +# keeping the items here until I submit PR to gitignore.io +*.sln.ide +*.bak +*.ncrunch* +*.Cache +[Ss]tyle[Cc]op.* + +# ----------------------------------------------------------------------- +# Created by https://www.gitignore.io/api/visualstudio + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + # User-specific files *.suo *.user +*.userosscache *.sln.docstates -*.sln.ide + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs # Build results [Dd]ebug/ +[Dd]ebugPublic/ [Rr]elease/ +[Rr]eleases/ x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + *_i.c *_p.c +*_i.h *.ilk *.meta *.obj @@ -36,36 +84,73 @@ x64/ *.tli *.tlh *.tmp +*.tmp_proj *.log -*.bak *.vspscc *.vssscc .builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in -_ReSharper* +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml # NCrunch -*.ncrunch* +_NCrunch_* .*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ -# Installshield output folder -[Ee]xpress +# Installshield output folder +[Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ @@ -78,38 +163,155 @@ DocProject/Help/Html2 DocProject/Help/html # Click-Once directory -publish +publish/ # Publish Web Output -*.Publish.xml +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Uncomment the next line to ignore your web deploy settings. +# By default, sensitive information, such as encrypted password +# should be stored in the .pubxml.user file. +#*.pubxml +*.pubxml.user +*.publishproj -# NuGet Packages Directory -packages +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ -# Windows Azure Build Output -csx +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ # Others -[Bb]in -[Oo]bj -sql -TestResults -[Tt]est[Rr]esult* -*.Cache -ClientBin -[Ss]tyle[Cc]op.* +ClientBin/ ~$* +*~ *.dbmdl -Generated_Code #added for RIA/Silverlight projects +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML +UpgradeLog*.htm -artifacts +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +### VisualStudio Patch ### +# By default, sensitive information, such as encrypted password +# should be stored in the .pubxml.user file. + +# End of https://www.gitignore.io/api/visualstudio +*.DS_Store diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..b321039f --- /dev/null +++ b/.mailmap @@ -0,0 +1,6 @@ +Filip W +Kristian Hellang +Justin Rusbatch +dschenkelman +Nick Berardi +estoy diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config deleted file mode 100644 index 67f8ea04..00000000 --- a/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe index f1cec45e..a34c3675 100644 Binary files a/.nuget/NuGet.exe and b/.nuget/NuGet.exe differ diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets deleted file mode 100644 index 83fe9060..00000000 --- a/.nuget/NuGet.targets +++ /dev/null @@ -1,136 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - - - - - $(SolutionDir).nuget - packages.config - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.nuget/packages.config b/.nuget/packages.config new file mode 100644 index 00000000..61ff56f9 --- /dev/null +++ b/.nuget/packages.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/.paket/Paket.Restore.targets b/.paket/Paket.Restore.targets new file mode 100644 index 00000000..305e736c --- /dev/null +++ b/.paket/Paket.Restore.targets @@ -0,0 +1,300 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + true + $(MSBuildThisFileDirectory) + $(MSBuildThisFileDirectory)..\ + $(PaketRootPath)paket-files\paket.restore.cached + $(PaketRootPath)paket.lock + /Library/Frameworks/Mono.framework/Commands/mono + mono + + $(PaketRootPath)paket.exe + $(PaketToolsPath)paket.exe + "$(PaketExePath)" + $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" + + + <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) + dotnet "$(PaketExePath)" + + + "$(PaketExePath)" + + $(PaketRootPath)paket.bootstrapper.exe + $(PaketToolsPath)paket.bootstrapper.exe + "$(PaketBootStrapperExePath)" + $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" + + + + + true + true + + + + + + + true + $(NoWarn);NU1603;NU1604;NU1605;NU1608 + + + + + /usr/bin/shasum "$(PaketRestoreCacheFile)" | /usr/bin/awk '{ print $1 }' + /usr/bin/shasum "$(PaketLockFilePath)" | /usr/bin/awk '{ print $1 }' + + + + + + + + + + + + + + + + $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) + $([System.IO.File]::ReadAllText('$(PaketLockFilePath)')) + true + false + true + + + + true + + + + + + + + + + + + + + + + $(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).paket.references.cached + + $(MSBuildProjectFullPath).paket.references + + $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references + + $(MSBuildProjectDirectory)\paket.references + + false + true + true + references-file-or-cache-not-found + + + + + $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) + $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) + references-file + false + + + + + false + + + + + true + target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) + + + + + + + + + + false + true + + + + + + + + + + + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) + + + %(PaketReferencesFileLinesInfo.PackageVersion) + All + runtime + true + + + + + $(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).paket.clitools + + + + + + + + + $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) + $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) + + + %(PaketCliToolFileLinesInfo.PackageVersion) + + + + + + + + + + false + + + + + + <_NuspecFilesNewLocation Include="$(BaseIntermediateOutputPath)$(Configuration)\*.nuspec"/> + + + + $(MSBuildProjectDirectory)/$(MSBuildProjectFile) + true + false + true + $(BaseIntermediateOutputPath)$(Configuration) + $(BaseIntermediateOutputPath) + + + + <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.nuspec"/> + + + + + + + + + + + + + + + + diff --git a/.paket/paket.bootstrapper.exe b/.paket/paket.bootstrapper.exe new file mode 100644 index 00000000..b98e000b Binary files /dev/null and b/.paket/paket.bootstrapper.exe differ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4f77655f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: csharp +dotnet: 2.0.0 +sudo: required +dist: trusty +env: + global: + - secure: m2PtYwYOhaK0uFMZ19ZxApZwWZeAIq1dS//jx/5I3txpIWD+TfycQMAWYxycFJ/GJkeVF29P4Zz1uyS2XKKjPJpp2Pds98FNQyDv3OftpLAVa0drsjfhurVlBmSdrV7GH6ncKfvhd+h7KVK5vbZc+NeR4dH7eNvN/jraS//AMJg= + - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true + - DOTNET_CLI_TELEMETRY_OPTOUT=true +mono: + - 5.8.0 + +os: + - linux + - osx + +script: + - ulimit -n8192 + - chmod +x build.sh + - ./build.sh \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ec4487e..52671d59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,31 +6,29 @@ We definitely want to soak up all that energy you have! ## The issue tracker and how you can contribute. -* Bugs - For bugs, just go fix it. Don't ask UNLESS it starts to turn into a feature. +* Bugs - For bugs, if you see one filed, just go fix it. If there is no bug filed, please file one first. You don't need to seek approval from core unless the bug starts to turn into a feature. _Any bug fix which is a significant code contribution (say more than 10 lines of code), which introduces new APIs or which introduces breaking changes will be treated in the same way as we treat a feature (see below)._ * Features - By default all issues tagged as enhancements which we file will be fixed by the core team. However if you'd like to take it, please comment in the issue that you'd like to implement it so we can discuss before you put in a potentially wasted effort. * YOU TAKE IT - These are items that are important, but which we'd available for the community to take. One place we use this is for investigations and prototyping for features that would be really awesome, but which the core team just don't have bandwith for. As an example @dschenkelman [investigated](https://github.com/scriptcs/scriptcs/issues/68?source=cc) a VS debugging story. This lead to him ultimately implementing the feature, however don't feel pressure that you have to that. It's extremely valuable if you can just show us how it might be done. -A few other points: +### Other points * If an issue is marked as "Taken" then it is assigned to another member of the community. -* To take an issue, reply in the comments "I'll take it" +* To take an issue, reply in the comments "I'll take it" and wait for confirmation from a member of the core team. ## Discussing features We'd like the design for all features to get socialized either via github issues or our [discussion group](https://groups.google.com/forum/?fromgroups#!forum/scriptcs). Please do this before you implement so that we can all get on the same page of the feature and not waste your time, which we really appreciate. -## Summary: +## Summary -* unassigned bug - available for you -* assigned bug / taken bug - unavailable -* you take it issue - available for you -* taken issue - unavailable as someone already took it. +* Unassigned bug - Available for you +* Assigned bug / Taken bug - unavailable +* You take it issue - Available for you once it is confirmed. +* Taken issue - Unavailable as someone already took it. We really appreciate your help in taking scriptcs forward! -Before submitting a feature or substantial code contribution please discuss it with the team in our - -Also please see the discussion [here] on which things are open for contribution (https://github.com/scriptcs/scriptcs/issues/79) +Also please see the discussion [here](https://github.com/scriptcs/scriptcs/issues/79) on which things are open for contribution. ## Workflow diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..fcb2be8d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + latest + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 00000000..eb7be3ec --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index e59e1189..7261cf31 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2013, 2014 Glenn Block, Justin Rusbatch, Filip Wojcieszyn +Copyright 2013 Glenn Block, Justin Rusbatch, Filip Wojcieszyn Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 383c72ed..01c0df77 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # scriptcs +[![Chocolatey Version](http://img.shields.io/chocolatey/v/scriptcs.svg?style=flat-square)](http://chocolatey.org/packages/scriptcs) [![Chocolatey Downloads](http://img.shields.io/chocolatey/dt/scriptcs.svg?style=flat-square)](http://chocolatey.org/packages/scriptcs) [![NuGet version (ScriptCs.Hosting)](https://img.shields.io/nuget/v/ScriptCs.Hosting.svg?style=flat-square)](https://www.nuget.org/packages/ScriptCs.Hosting/) + +[![*nix Build Status](http://img.shields.io/travis/scriptcs/scriptcs/dev.svg?style=flat-square&label=linux-build)](https://travis-ci.org/scriptcs/scriptcs) [![Windows Build Status](http://img.shields.io/teamcity/codebetter/Scriptcs_Ci.svg?style=flat-square&label=windows-build)](http://ci.scriptcs.net) [![Coverity Scan Build Status](https://img.shields.io/badge/coverity-passed-brightgreen.svg?style=flat-square)](https://scan.coverity.com/projects/2356) + +[![Issue Stats](http://issuestats.com/github/scriptcs/scriptcs/badge/pr?style=flat-square)](http://issuestats.com/github/scriptcs/scriptcs) [![Issue Stats](http://issuestats.com/github/scriptcs/scriptcs/badge/issue?style=flat-square)](http://issuestats.com/github/scriptcs/scriptcs) ## What is it? @@ -21,13 +26,19 @@ Releases and nightly builds should be installed using [Chocolatey](http://chocol @powershell -NoProfile -ExecutionPolicy Unrestricted -Command "iex ((New-Object Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin +If the above fails with the error indicating that proxy authentication is required (i.e. [HTTP 407](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8)) then try again with the following on the command prompt that uses your default credentials: + + @powershell -NoProfile -ExecutionPolicy Unrestricted -Command "[Net.WebRequest]::DefaultWebProxy.Credentials = [Net.CredentialCache]::DefaultCredentials; iex ((New-Object Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin + +**Note:** If you are using a version of Chocolatey > 0.9.9.0 you can pass the `-y` into the install and upgrade commands to prevent the confirmation that will appear. + ### Installing scriptcs Once Chocolatey has been installed, you can install the latest stable version of scriptcs from your command prompt: - cinst scriptcs + choco install scriptcs -Chocolatey will install scriptcs to `%APPDATA%\scriptcs\` and update your PATH accordingly. +Chocolatey will install scriptcs to `%LOCALAPPDATA%\scriptcs\` and update your PATH accordingly. **Note:** You may need to restart your command prompt after the installation completes. @@ -35,18 +46,33 @@ Chocolatey will install scriptcs to `%APPDATA%\scriptcs\` and update your PATH a With Chocolatey, keeping scriptcs updated is just as easy: - cup scriptcs + choco upgrade scriptcs + +**Note:** If you are using a version of Chocolatey < 0.9.0.0 you will need to use `choco update scriptcs`, but also think about updating Chocolatey itself. ### Nightly builds Nightly builds are hosted on [MyGet](https://www.myget.org/), and can also be installed through with Chocolatey: - cinst scriptcs -pre -source https://www.myget.org/F/scriptcsnightly/ + choco install scriptcs -pre -source https://www.myget.org/F/scriptcsnightly/ ### Building from source -Execute `build.cmd` to start the build script. +#### Windows + +1. Ensure you have .NET Framework 4.6.1 installed. + +2. Execute the build script. + `build.cmd` + +#### Linux + +1. Ensure you have [Mono 5.12](https://www.mono-project.com/download/stable/) or later installed. + +2. Execute the build script + + `./build.sh` ## Getting Started @@ -65,7 +91,7 @@ Hello, world! C:\> ``` -REPL supports all C# language constructs (i.e. class defnition, method definition), as well as multi-line input. For example: +REPL supports all C# language constructs (i.e. class definition, method definition), as well as multi-line input. For example: ```batchfile C:\> scriptcs @@ -92,22 +118,12 @@ using Raven.Client.Indexes; Console.WriteLine("Starting RavenDB server..."); -EmbeddableDocumentStore documentStore = null; -try +using (var documentStore = new EmbeddableDocumentStore { UseEmbeddedHttpServer = true }) { - documentStore = new EmbeddableDocumentStore { UseEmbeddedHttpServer = true }; documentStore.Initialize(); - - var url = string.Format("http://localhost:{0}", documentStore.Configuration.Port); - Console.WriteLine("RavenDB started, listening on {0}.", url); - + Console.WriteLine($"RavenDB started, listening on http://localhost:{documentStore.Configuration.Port}"); Console.ReadKey(); } -finally -{ - if (documentStore != null) - documentStore.Dispose(); -} ``` * Install the [RavenDB.Embedded](https://nuget.org/packages/RavenDB.Embedded/) package from NuGet using the [install command](https://github.com/scriptcs/scriptcs/wiki/Package-installation). @@ -133,7 +149,7 @@ RavenDB started, listening on http://localhost:8080. Script Packs can be used to further reduce the amount of code you need to write when working with common frameworks. -* In an empty directory, install the [ScriptCs.WebApi](https://nuget.org/packages/ScriptCs.WebApi/) script pack from NuGet. The script pack will automatically imports the Web API namespaces and provides a convenient factory method for initializing the Web API host. It also replaces the default `ControllerResolver` with a custom implementation that allows Web API to discover controllers declared in scripts. +* In an empty directory, install the [ScriptCs.WebApi](https://nuget.org/packages/ScriptCs.WebApi/) script pack from NuGet. The script pack automatically imports the Web API namespaces and provides a convenient factory method for initializing the Web API host. It also replaces the default `ControllerResolver` with a custom implementation that allows Web API to discover controllers declared in scripts. ```batchfile scriptcs -install ScriptCs.WebApi @@ -213,11 +229,11 @@ Instructions for debugging scripts using Visual Studio can be found on the [wiki ### Package installation -You can install any NuGet packages directly from the scriptcs CLI. This will pull the relevant packages from NuGet, and install them in the packages folder. +You can install any NuGet packages directly from the scriptcs CLI. This will pull the relevant packages from NuGet, and install them in the scriptcs_packages folder. -Once the packages are installed, you can simply start using them in your script code directly (just import the namespaces - no additional bootstraping or DLL referencing is needed). +Once the packages are installed, you can simply start using them in your script code directly (just import the namespaces - no additional bootstrapping or DLL referencing is needed). -The `install` command will also create a `packages.config` file if you don't have one - so that you can easily redistribute your script (without having to copy the package binaries). +The `install` command will also create a `scriptcs_packages.config` file if you don't have one - so that you can easily redistribute your script (without having to copy the package binaries). - `scriptcs -install {package name}` will install the desired package from NuGet. @@ -225,9 +241,9 @@ The `install` command will also create a `packages.config` file if you don't hav scriptcs -install ServiceStack - - `scriptcs -install` (without package name) will look for the `packages.config` file located in the current execution directory, and install all the packages specified there. You only need to specify **top level** packages. + - `scriptcs -install` (without package name) will look for the `scriptcs_packages.config` file located in the current execution directory, and install all the packages specified there. You only need to specify **top level** packages. -For example, you might create the following `packages.config`: +For example, you might create the following `scriptcs_packages.config`: @@ -240,7 +256,7 @@ And then just call: scriptcs -install -As a result, all packages specified in the `packages.config`, including all dependencies, will be downloaded and installed in the `packages` folder. +As a result, all packages specified in the `scriptcs_packages.config`, including all dependencies, will be downloaded and installed in the `scriptcs_packages` folder. ## Contributing @@ -269,7 +285,8 @@ Want to chat? In addition to Twitter, you can find us on [Google Groups](https:/ * [Damian Schenkelman](http://github.com/dschenkelman) ([@dschenkelman](https://twitter.com/intent/user?screen_name=dschenkelman)) * [Kristian Hellang](http://github.com/khellang) ([@khellang](https://twitter.com/intent/user?screen_name=khellang)) - +* [Adam Ralph](http://github.com/adamralph) ([@adamralph](https://twitter.com/intent/user?screen_name=adamralph)) +* [Paul Bouwer](http://github.com/paulbouwer) ([@pbouwer](https://twitter.com/intent/user?screen_name=pbouwer)) ## Credits diff --git a/ScriptCs.Test.ruleset b/ScriptCs.Test.ruleset deleted file mode 100644 index c99131d0..00000000 --- a/ScriptCs.Test.ruleset +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/ScriptCs.ruleset b/ScriptCs.ruleset deleted file mode 100644 index 0abac465..00000000 --- a/ScriptCs.ruleset +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ScriptCs.sln b/ScriptCs.sln index 210b972b..756ee82f 100644 --- a/ScriptCs.sln +++ b/ScriptCs.sln @@ -1,41 +1,40 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs", "src\ScriptCs\ScriptCs.csproj", "{25080671-1A80-4041-B9C7-260578FF4849}" +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs", "src\ScriptCs\ScriptCs.csproj", "{25080671-1A80-4041-B9C7-260578FF4849}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Contracts", "src\ScriptCs.Contracts\ScriptCs.Contracts.csproj", "{6049E205-8B5F-4080-B023-70600E51FD64}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Contracts", "src\ScriptCs.Contracts\ScriptCs.Contracts.csproj", "{6049E205-8B5F-4080-B023-70600E51FD64}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Tests", "test\ScriptCs.Tests\ScriptCs.Tests.csproj", "{4D6A2A55-BB17-40CB-9567-EAFF80BEFDCE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Tests", "test\ScriptCs.Tests\ScriptCs.Tests.csproj", "{4D6A2A55-BB17-40CB-9567-EAFF80BEFDCE}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A59C6538-62EA-4BF6-AA00-E0E9A2892D47}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{E95E5426-BF99-458E-AE6F-544DBBA9BC9E}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Core", "src\ScriptCs.Core\ScriptCs.Core.csproj", "{E590E710-E159-48E6-A3E6-1A83D3FE732C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Core.Tests", "test\ScriptCs.Core.Tests\ScriptCs.Core.Tests.csproj", "{AC228213-7356-4F0D-BA48-EBA5FB8A7506}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Engine.Roslyn.Tests", "test\ScriptCs.Engine.Roslyn.Tests\ScriptCs.Engine.Roslyn.Tests.csproj", "{28D11DE5-9F98-4E0A-8CCC-9CDC19110451}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Core", "src\ScriptCs.Core\ScriptCs.Core.csproj", "{E590E710-E159-48E6-A3E6-1A83D3FE732C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Hosting", "src\ScriptCs.Hosting\ScriptCs.Hosting.csproj", "{9AEF2D95-87FB-4829-B384-34BFE076D531}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Core.Tests", "test\ScriptCs.Core.Tests\ScriptCs.Core.Tests.csproj", "{AC228213-7356-4F0D-BA48-EBA5FB8A7506}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Hosting.Tests", "test\ScriptCs.Hosting.Tests\ScriptCs.Hosting.Tests.csproj", "{EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{9659F354-CF48-4FD6-8E0C-E9EB3C2BDA32}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{B138045D-DC04-4A04-A2BA-F771173BCC32}" ProjectSection(SolutionItems) = preProject - build\Build.proj = build\Build.proj - build\ScriptCs.Tasks.targets = build\ScriptCs.Tasks.targets - build\ScriptCs.Common.props = build\ScriptCs.Common.props - ScriptCs.ruleset = ScriptCs.ruleset - build\ScriptCs.Version.props = build\ScriptCs.Version.props + .nuget\packages.config = .nuget\packages.config EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Engine.Roslyn", "src\ScriptCs.Engine.Roslyn\ScriptCs.Engine.Roslyn.csproj", "{E79EC231-E27D-4057-91C9-2D001A3A8C3B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Engine.Roslyn", "src\ScriptCs.Engine.Roslyn\ScriptCs.Engine.Roslyn.csproj", "{E79EC231-E27D-4057-91C9-2D001A3A8C3B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Engine.Roslyn.Tests", "test\ScriptCs.Engine.Roslyn.Tests\ScriptCs.Engine.Roslyn.Tests.csproj", "{28D11DE5-9F98-4E0A-8CCC-9CDC19110451}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptCs.Tests.Acceptance", "test\ScriptCs.Tests.Acceptance\ScriptCs.Tests.Acceptance.csproj", "{10684649-2922-41F5-AB9B-20B127CBF92C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Hosting", "src\ScriptCs.Hosting\ScriptCs.Hosting.csproj", "{9AEF2D95-87FB-4829-B384-34BFE076D531}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptCs.Hosting.Tests", "test\ScriptCs.Hosting.Tests\ScriptCs.Hosting.Tests.csproj", "{EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C5ABCEF-82F9-4850-B2B8-1D7B73D9C5C3}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -63,10 +62,6 @@ Global {AC228213-7356-4F0D-BA48-EBA5FB8A7506}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC228213-7356-4F0D-BA48-EBA5FB8A7506}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC228213-7356-4F0D-BA48-EBA5FB8A7506}.Release|Any CPU.Build.0 = Release|Any CPU - {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Release|Any CPU.Build.0 = Release|Any CPU {28D11DE5-9F98-4E0A-8CCC-9CDC19110451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {28D11DE5-9F98-4E0A-8CCC-9CDC19110451}.Debug|Any CPU.Build.0 = Debug|Any CPU {28D11DE5-9F98-4E0A-8CCC-9CDC19110451}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -79,6 +74,14 @@ Global {EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A}.Release|Any CPU.Build.0 = Release|Any CPU + {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E79EC231-E27D-4057-91C9-2D001A3A8C3B}.Release|Any CPU.Build.0 = Release|Any CPU + {10684649-2922-41F5-AB9B-20B127CBF92C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10684649-2922-41F5-AB9B-20B127CBF92C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10684649-2922-41F5-AB9B-20B127CBF92C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10684649-2922-41F5-AB9B-20B127CBF92C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -88,5 +91,9 @@ Global {AC228213-7356-4F0D-BA48-EBA5FB8A7506} = {A59C6538-62EA-4BF6-AA00-E0E9A2892D47} {28D11DE5-9F98-4E0A-8CCC-9CDC19110451} = {A59C6538-62EA-4BF6-AA00-E0E9A2892D47} {EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A} = {A59C6538-62EA-4BF6-AA00-E0E9A2892D47} + {10684649-2922-41F5-AB9B-20B127CBF92C} = {A59C6538-62EA-4BF6-AA00-E0E9A2892D47} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AD80B19D-7299-4CBD-B59F-6DB868FF6A9C} EndGlobalSection EndGlobal diff --git a/ScriptCs.sln.DotSettings b/ScriptCs.sln.DotSettings new file mode 100644 index 00000000..993cd6d0 --- /dev/null +++ b/ScriptCs.sln.DotSettings @@ -0,0 +1,2 @@ + + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..55cad9e8 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,16 @@ +version: 1.0.{build} + +image: Visual Studio 2017 + +environment: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +init: +- git config --global core.autocrlf false + +build_script: +- cmd: + build.cmd + +test: off \ No newline at end of file diff --git a/build.cmd b/build.cmd index 3d142e81..752c9f63 100644 --- a/build.cmd +++ b/build.cmd @@ -1,27 +1,14 @@ -@echo Off -setlocal +@echo off +cls -set EnableNuGetPackageRestore=true +.paket\paket.bootstrapper.exe +if errorlevel 1 ( + exit /b %errorlevel% +) -if exist artifacts goto Build -mkdir artifacts +.paket\paket.exe restore +if errorlevel 1 ( + exit /b %errorlevel% +) -:Build -%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Build\Build.proj /nologo /m /v:M %* /fl /flp:LogFile=artifacts\msbuild.log;Verbosity=Detailed;DetailedSummary /nr:false - -if %ERRORLEVEL% neq 0 goto BuildFail -goto BuildSuccess - -:BuildFail -echo. -echo *** BUILD FAILED *** -goto End - -:BuildSuccess -echo. -echo **** BUILD SUCCESSFUL *** -goto end - -:End -echo. -exit /b %ERRORLEVEL% \ No newline at end of file +packages\FAKE\tools\FAKE.exe build.fsx %* \ No newline at end of file diff --git a/build.fsx b/build.fsx new file mode 100644 index 00000000..51ba6692 --- /dev/null +++ b/build.fsx @@ -0,0 +1,70 @@ +#r "packages/FAKE/tools/FakeLib.dll" + +open Fake +open Fake.DotNetCli +open Fake.Testing +open System.IO + +Target "Clean" (fun _ -> + !! "artifacts" ++ "src/*/bin" ++ "test/*/bin" ++ "src/*/obj" ++ "test/*/obj" + |> DeleteDirs +) + +Target "Build" (fun _ -> + DotNetCli.Restore id + + "ScriptCs.sln" + |> MSBuildHelper.build (fun p -> + { p with + RestorePackagesFlag = true + Verbosity = Some Minimal + Targets = [ "Build" ] + Properties = + [ + "Optimize", "True" + "Configuration", "Release" + ] + } ) +) + +Target "Test" (fun _ -> +#if MONO + !! "test/**/bin/**/*Tests.Acceptance.dll" + |> xUnit2 (fun c -> + {c with + MaxThreads = CollectionConcurrencyMode.MaxThreads 1 + }) +#else + !! "test/**/*Tests*.csproj" + |> Seq.iter (fun p -> + DotNetCli.Test (fun c -> + {c with + WorkingDir = Path.GetDirectoryName p + AdditionalArgs = ["--no-build"] + }) + ) +#endif +) + +Target "Pack" (fun _ -> + "ScriptCs.sln" + |> MSBuildHelper.build (fun p -> + { p with + RestorePackagesFlag = true + Verbosity = Some Minimal + Targets = [ "Pack" ] + Properties = + [ + "Optimize", "True" + "Configuration", "Release" + "PackageOutputPath", "../../artifacts" + ] + } ) +) + +"Clean" + ==> "Build" + ==> "Test" + ==> "Pack" + +RunTargetOrDefault "Pack" \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..fa37cd79 --- /dev/null +++ b/build.sh @@ -0,0 +1,33 @@ +#!/bin/bash +if test "$OS" = "Windows_NT" +then + # use .Net + + .paket/paket.bootstrapper.exe prerelease + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + .paket/paket.exe restore + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + packages/FAKE/tools/FAKE.exe $@ --nocache --fsiargs build.fsx +else + # use mono + mono .paket/paket.bootstrapper.exe prerelease + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + mono .paket/paket.exe restore + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + mono packages/FAKE/tools/FAKE.exe $@ --nocache --fsiargs -d:MONO build.fsx +fi \ No newline at end of file diff --git a/build/Build.proj b/build/Build.proj deleted file mode 100644 index e8d0abc5..00000000 --- a/build/Build.proj +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - Release - - $(MSBuildThisFileDirectory)..\ - $(Root)artifacts\ - $(BaseArtifactsPath)$(Configuration)\ - $(BaseArtifactsPath)$(Configuration) - - $(Root).nuget\ - $(NuGetPath)NuGet.exe - $(NuGetPath)NuGet.targets - - $([System.IO.Path]::Combine( $(Root), 'common\CommonVersionInfo.cs' )) - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeAnalysis\ - $(CodeAnalysisToolPath)Microsoft.CodeAnalysis.targets - $(Root) - - - - - - - - Configuration=$(Configuration); - ArtifactsPath=$(ArtifactsPath); - RunCodeAnalysis=false; - RestorePackages=false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Version $(PackageVersion) -Symbols -Verbosity quiet -NoPackageAnalysis -OutputDirectory "$(PackageOutputPath)" -p Configuration=$(Configuration) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/ScriptCs.Common.props b/build/ScriptCs.Common.props deleted file mode 100644 index 8f933028..00000000 --- a/build/ScriptCs.Common.props +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - Debug - AnyCPU - - Properties - v4.5 - 512 - - ..\..\ - true - - true - prompt - 4 - bin\$(Configuration)\ - - true - - TRACE;$(DefineConstants) - - $(SolutionDir)ScriptCs.ruleset - false - - - - false - - - - DEBUG;CODE_ANALYSIS;$(DefineConstants) - full - false - - - - pdbonly - true - - \ No newline at end of file diff --git a/build/ScriptCs.Tasks.targets b/build/ScriptCs.Tasks.targets deleted file mode 100644 index 0ebad435..00000000 --- a/build/ScriptCs.Tasks.targets +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - File.Exists(x.GetMetadata("FullPath"))) - .Select(x => XDocument.Load(x.GetMetadata("FullPath"))) - .SelectMany(x => x.Root.Elements("package")) - .Select(x => new { Id = x.Attribute("id").Value, Version = x.Attribute("version").Value }) - .Distinct() - .OrderBy(x => x.Id); - - var document = - new XDocument( - new XDeclaration("1.0", "utf-8", "true"), - new XElement("packages", - packages.Select(x => new XElement("package", new XAttribute("id", x.Id), new XAttribute("version", x.Version))))); - - document.Save(OutputPath); - ]]> - - - - \ No newline at end of file diff --git a/build/ScriptCs.Version.props b/build/ScriptCs.Version.props deleted file mode 100644 index ca12c3bb..00000000 --- a/build/ScriptCs.Version.props +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - 0 - 9 - 0 - - - - alpha - - - - - $(MajorVersion).$(MinorVersion).0 - $(MajorVersion).$(MinorVersion).$(PatchVersion) - $(AssemblyInformationalVersion)-$(BuildQuality) - - $(AssemblyInformationalVersion) - $(PackageVersion)-$([System.DateTime]::UtcNow.ToString("yyMMdd")) - - - - - <_Parameter1>$(AssemblyVersion) - - - <_Parameter1>$(AssemblyInformationalVersion) - - - diff --git a/build_brew.sh b/build_brew.sh new file mode 100755 index 00000000..b74b80e4 --- /dev/null +++ b/build_brew.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +set -o pipefail +set -x + +# install +mozroots --import --sync --quiet +mono ./.nuget/NuGet.exe restore ./ScriptCs.sln + +# script +mkdir -p artifacts/Release/bin +msbuild ./ScriptCs.sln /property:Configuration=Release /nologo /verbosity:normal +cp src/ScriptCs/bin/Release/net461/* artifacts/Release/bin/ diff --git a/common/CommonAssemblyInfo.cs b/common/CommonAssemblyInfo.cs deleted file mode 100644 index b78de3c3..00000000 --- a/common/CommonAssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -#if DEBUG -[assembly: AssemblyConfiguration("Debug")] -#else -[assembly: AssemblyConfiguration("Release")] -#endif - -[assembly: AssemblyProduct("scriptcs")] -[assembly: AssemblyCopyright("Copyright 2013 Glenn Block, Justin Rusbatch, Filip Wojcieszyn")] - -[assembly: ComVisible(false)] -[assembly: CLSCompliant(false)] \ No newline at end of file diff --git a/common/CommonVersionInfo.cs b/common/CommonVersionInfo.cs deleted file mode 100644 index e8a62e22..00000000 --- a/common/CommonVersionInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.18051 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyVersionAttribute("0.6.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("0.6.0-alpha")] - -// Generated by the MSBuild WriteCodeFragment class on 7/25/2013 4:08:10 AM. - diff --git a/common/Icon.ico b/common/Icon.ico deleted file mode 100644 index d946fa05..00000000 Binary files a/common/Icon.ico and /dev/null differ diff --git a/paket.dependencies b/paket.dependencies new file mode 100644 index 00000000..a397754e --- /dev/null +++ b/paket.dependencies @@ -0,0 +1,4 @@ +source https://nuget.org/api/v2 + +nuget FAKE +nuget xunit.runner.console \ No newline at end of file diff --git a/paket.lock b/paket.lock new file mode 100644 index 00000000..a878f015 --- /dev/null +++ b/paket.lock @@ -0,0 +1,4 @@ +NUGET + remote: https://www.nuget.org/api/v2 + FAKE (4.64.13) + xunit.runner.console (2.4.1) diff --git a/src/ScriptCs.Contracts/AssemblyReferences.cs b/src/ScriptCs.Contracts/AssemblyReferences.cs index b9c28929..734bcea7 100644 --- a/src/ScriptCs.Contracts/AssemblyReferences.cs +++ b/src/ScriptCs.Contracts/AssemblyReferences.cs @@ -1,30 +1,110 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace ScriptCs.Contracts +namespace ScriptCs.Contracts { + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System; + using System.IO; + public class AssemblyReferences { + private readonly Dictionary _assemblies = new Dictionary(); + private readonly Dictionary _paths = new Dictionary(); + public AssemblyReferences() + : this(Enumerable.Empty()) + { + } + + public AssemblyReferences(IEnumerable assemblies) + : this(assemblies, Enumerable.Empty()) { - PathReferences = new HashSet(); - Assemblies = new HashSet(); } - public HashSet PathReferences { get; set; } - public HashSet Assemblies { get; set; } + public AssemblyReferences(IEnumerable paths) + : this(Enumerable.Empty(), paths) + { + } - public AssemblyReferences Except(AssemblyReferences obj) + public AssemblyReferences(IEnumerable assemblies, IEnumerable paths) { - Guard.AgainstNullArgument("obj", obj); + Guard.AgainstNullArgument("paths", paths); + Guard.AgainstNullArgument("assemblies", assemblies); + + foreach (var assembly in assemblies.Where(assembly => assembly != null)) + { + var name = assembly.GetName().Name; + if (!_assemblies.ContainsKey(name)) + { + _assemblies.Add(name, assembly); + } + } - var deltaObject = new AssemblyReferences + foreach (var path in paths) + { + var name = Path.GetFileName(path); + if (name == null) { - PathReferences = new HashSet(PathReferences.Except(obj.PathReferences)), - Assemblies = new HashSet(Assemblies.Except(obj.Assemblies)) - }; - return deltaObject; + continue; + } + + if (name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || + name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + name = Path.GetFileNameWithoutExtension(name); + } + + if (!_paths.ContainsKey(name) && !_assemblies.ContainsKey(name)) + { + _paths.Add(name, path); + } + } + } + + public IEnumerable Assemblies => _assemblies.Values.ToArray(); + + public IEnumerable Paths => _paths.Values.ToArray(); + + public AssemblyReferences Union(AssemblyReferences references) + { + Guard.AgainstNullArgument("references", references); + + return new AssemblyReferences(Assemblies.Union(references.Assemblies), Paths.Union(references.Paths)); + } + + public AssemblyReferences Union(IEnumerable assemblies) + { + Guard.AgainstNullArgument("assemblies", assemblies); + + return new AssemblyReferences(Assemblies.Union(assemblies), Paths); + } + + public AssemblyReferences Union(IEnumerable paths) + { + Guard.AgainstNullArgument("paths", paths); + + return new AssemblyReferences(Assemblies, Paths.Union(paths)); + } + + public AssemblyReferences Except(AssemblyReferences references) + { + Guard.AgainstNullArgument("references", references); + + return new AssemblyReferences(Assemblies.Except(references.Assemblies), Paths.Except(references.Paths)); + } + + public AssemblyReferences Except(IEnumerable assemblies) + { + Guard.AgainstNullArgument("assemblies", assemblies); + + return new AssemblyReferences(Assemblies.Except(assemblies), Paths); + } + + public AssemblyReferences Except(IEnumerable paths) + { + Guard.AgainstNullArgument("paths", paths); + + return new AssemblyReferences(Assemblies, Paths.Except(paths)); } } -} \ No newline at end of file +} diff --git a/src/Scriptcs.Contracts/BehaviorAfterCode.cs b/src/ScriptCs.Contracts/BehaviorAfterCode.cs similarity index 100% rename from src/Scriptcs.Contracts/BehaviorAfterCode.cs rename to src/ScriptCs.Contracts/BehaviorAfterCode.cs diff --git a/src/ScriptCs.Contracts/DefaultLogProvider.cs b/src/ScriptCs.Contracts/DefaultLogProvider.cs new file mode 100644 index 00000000..202dae56 --- /dev/null +++ b/src/ScriptCs.Contracts/DefaultLogProvider.cs @@ -0,0 +1,76 @@ +namespace ScriptCs.Contracts +{ + using System; + + public class DefaultLogProvider : ILogProvider + { + private readonly ILogProvider _provider = ResolveLogProvider() ?? new NullLogProvider(); + + [Obsolete("Should not be called directly. Instead, call a method on LogProviderExtensions.")] + public Logger GetLogger(string name) => _provider.GetLogger(name); + + /// + /// Opens a nested diagnostics context. Not supported in . + /// + /// The message to add to the diagnostics context. + /// A disposable that when disposed removes the message from the context. + public IDisposable OpenNestedContext(string message) => _provider.OpenNestedContext(message); + + /// + /// Opens a mapped diagnostics context. Not supported in . + /// + /// A key. + /// A value. + /// A disposable that when disposed removes the map from the context. + public IDisposable OpenMappedContext(string key, string value) => _provider.OpenMappedContext(key, value); + + private static ILogProvider ResolveLogProvider() + { + var libLogProvider = LibLog.LogProvider.ResolveLogProvider(); + return libLogProvider == null ? null : new LibLogAdapter(libLogProvider); + } + + private class NullLogProvider : ILogProvider + { + private static readonly Logger logger = (_, __, ___, ____) => false; + private static readonly Disposable disposable = new Disposable(); + + [Obsolete("Should not be called directly. Instead, call a method on LogProviderExtensions.")] + public Logger GetLogger(string name) + { + return logger; + } + + public IDisposable OpenNestedContext(string message) => disposable; + + public IDisposable OpenMappedContext(string key, string value) => disposable; + + private sealed class Disposable : IDisposable + { + public void Dispose() + { + } + } + } + + private class LibLogAdapter : ILogProvider + { + private readonly LibLog.ILogProvider _libLogProvider; + + public LibLogAdapter(LibLog.ILogProvider libLogProvider) + { + _libLogProvider = libLogProvider; + } + + public Logger GetLogger(string name) + { + return (logLevel, messageFunc, exception, formatParameters) => + _libLogProvider.GetLogger(name)((LibLog.LogLevel)logLevel, messageFunc, exception, formatParameters); + } + + public IDisposable OpenNestedContext(string message) => _libLogProvider.OpenNestedContext(message); + + public IDisposable OpenMappedContext(string key, string value) => _libLogProvider.OpenMappedContext(key, value); + } + } +} diff --git a/src/ScriptCs.Contracts/DirectiveLineProcessor.cs b/src/ScriptCs.Contracts/DirectiveLineProcessor.cs index c7cbb20c..9494a54b 100644 --- a/src/ScriptCs.Contracts/DirectiveLineProcessor.cs +++ b/src/ScriptCs.Contracts/DirectiveLineProcessor.cs @@ -1,24 +1,18 @@ -using System; -using ScriptCs.Contracts.Exceptions; +using ScriptCs.Contracts.Exceptions; + namespace ScriptCs.Contracts { - public abstract class DirectiveLineProcessor : ILineProcessor + public abstract class DirectiveLineProcessor : IDirectiveLineProcessor { - protected virtual BehaviorAfterCode BehaviorAfterCode - { - get { return BehaviorAfterCode.Ignore; } - } + protected virtual BehaviorAfterCode BehaviorAfterCode => BehaviorAfterCode.Ignore; protected abstract string DirectiveName { get; } - private string DirectiveString - { - get { return string.Format("#{0} ", DirectiveName); } - } + private string DirectiveString => string.Format("#{0}", DirectiveName); public bool ProcessLine(IFileParser parser, FileParserContext context, string line, bool isBeforeCode) { - if (!IsDirective(line)) + if (!Matches(line)) { return false; } @@ -27,13 +21,13 @@ public bool ProcessLine(IFileParser parser, FileParserContext context, string li { if (BehaviorAfterCode == Contracts.BehaviorAfterCode.Throw) { - throw new InvalidDirectiveUseException(string.Format("Encountered {0}directive after the start of code. Please move this directive to the beginning of the file.", DirectiveString)); + throw new InvalidDirectiveUseException(string.Format("Encountered directive '{0}' after the start of code. Please move this directive to the beginning of the file.", DirectiveString)); } - else if (BehaviorAfterCode == Contracts.BehaviorAfterCode.Ignore) + if (BehaviorAfterCode == Contracts.BehaviorAfterCode.Ignore) { return true; } - } + } return ProcessLine(parser, context, line); } @@ -43,16 +37,19 @@ protected string GetDirectiveArgument(string line) Guard.AgainstNullArgument("line", line); return line.Replace(DirectiveString, string.Empty) - .Trim(' ') + .Trim() .Replace("\"", string.Empty) .Replace(";", string.Empty); } protected abstract bool ProcessLine(IFileParser parser, FileParserContext context, string line); - private bool IsDirective(string line) + public bool Matches(string line) { - return line.Trim(' ').StartsWith(DirectiveString); + Guard.AgainstNullArgument("line", line); + + var tokens = line.Split(); + return tokens[0] == DirectiveString; } } } \ No newline at end of file diff --git a/src/Scriptcs.Contracts/Exceptions/InvalidDirectiveUseException.cs b/src/ScriptCs.Contracts/Exceptions/InvalidDirectiveUseException.cs similarity index 100% rename from src/Scriptcs.Contracts/Exceptions/InvalidDirectiveUseException.cs rename to src/ScriptCs.Contracts/Exceptions/InvalidDirectiveUseException.cs diff --git a/src/ScriptCs.Contracts/FileParserContext.cs b/src/ScriptCs.Contracts/FileParserContext.cs index 312a6f5f..adc754d8 100644 --- a/src/ScriptCs.Contracts/FileParserContext.cs +++ b/src/ScriptCs.Contracts/FileParserContext.cs @@ -19,5 +19,7 @@ public FileParserContext() public List LoadedScripts { get; private set; } public List BodyLines { get; private set; } + + public string ScriptPath { get; set; } } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/FilePreProcessorResult.cs b/src/ScriptCs.Contracts/FilePreProcessorResult.cs index 7406b3a3..2b664edc 100644 --- a/src/ScriptCs.Contracts/FilePreProcessorResult.cs +++ b/src/ScriptCs.Contracts/FilePreProcessorResult.cs @@ -17,6 +17,8 @@ public FilePreProcessorResult() public List References { get; set; } + public string ScriptPath { get; set; } + public string Code { get; set; } } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/FileSystemExtensions.cs b/src/ScriptCs.Contracts/FileSystemExtensions.cs new file mode 100644 index 00000000..765d8e9f --- /dev/null +++ b/src/ScriptCs.Contracts/FileSystemExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace ScriptCs.Contracts +{ + public static class FileSystemExtensions + { + public static IEnumerable EnumerateBinaries( + this IFileSystem fileSystem, string path, SearchOption searchOption = SearchOption.AllDirectories) + { + Guard.AgainstNullArgument("fileSystem", fileSystem); + + return fileSystem.EnumerateFiles(path, "*.dll", searchOption) + .Union(fileSystem.EnumerateFiles(path, "*.exe", searchOption)) + .Where(f=>!f.Equals("scriptcs.exe", StringComparison.InvariantCultureIgnoreCase)); + } + } +} diff --git a/src/ScriptCs.Contracts/IAppDomainAssemblyResolver.cs b/src/ScriptCs.Contracts/IAppDomainAssemblyResolver.cs new file mode 100644 index 00000000..ff805473 --- /dev/null +++ b/src/ScriptCs.Contracts/IAppDomainAssemblyResolver.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace ScriptCs.Contracts +{ + public interface IAppDomainAssemblyResolver + { + void Initialize(); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IAssemblyResolver.cs b/src/ScriptCs.Contracts/IAssemblyResolver.cs index 78465b8f..cc3e5320 100644 --- a/src/ScriptCs.Contracts/IAssemblyResolver.cs +++ b/src/ScriptCs.Contracts/IAssemblyResolver.cs @@ -4,6 +4,6 @@ namespace ScriptCs.Contracts { public interface IAssemblyResolver { - IEnumerable GetAssemblyPaths(string path); + IEnumerable GetAssemblyPaths(string path, bool binariesOnly = false); } } diff --git a/src/ScriptCs.Contracts/IAssemblyUtility.cs b/src/ScriptCs.Contracts/IAssemblyUtility.cs index f9aae23c..8d545730 100644 --- a/src/ScriptCs.Contracts/IAssemblyUtility.cs +++ b/src/ScriptCs.Contracts/IAssemblyUtility.cs @@ -1,7 +1,12 @@ -namespace ScriptCs.Contracts +using System.Reflection; + +namespace ScriptCs.Contracts { public interface IAssemblyUtility { - bool IsManagedAssembly(string assemblyPath); + bool IsManagedAssembly(string path); + Assembly LoadFile(string path); + Assembly Load(AssemblyName assemblyRef); + AssemblyName GetAssemblyName(string path); } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IConsole.cs b/src/ScriptCs.Contracts/IConsole.cs index be06d0d9..2ff69cdc 100644 --- a/src/ScriptCs.Contracts/IConsole.cs +++ b/src/ScriptCs.Contracts/IConsole.cs @@ -10,7 +10,7 @@ public interface IConsole void WriteLine(string value); - string ReadLine(); + string ReadLine(string prompt); void Clear(); @@ -19,5 +19,7 @@ public interface IConsole void ResetColor(); ConsoleColor ForegroundColor { get; set; } + + int Width { get; } } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IDirectiveLineProcessor.cs b/src/ScriptCs.Contracts/IDirectiveLineProcessor.cs new file mode 100644 index 00000000..41331ec3 --- /dev/null +++ b/src/ScriptCs.Contracts/IDirectiveLineProcessor.cs @@ -0,0 +1,7 @@ +namespace ScriptCs.Contracts +{ + public interface IDirectiveLineProcessor : ILineProcessor + { + bool Matches(string line); + } +} diff --git a/src/ScriptCs.Contracts/IFileSystem.cs b/src/ScriptCs.Contracts/IFileSystem.cs index 0ccc6973..0f0fb352 100644 --- a/src/ScriptCs.Contracts/IFileSystem.cs +++ b/src/ScriptCs.Contracts/IFileSystem.cs @@ -6,10 +6,19 @@ namespace ScriptCs.Contracts { public interface IFileSystem { - IEnumerable EnumerateFiles(string dir, string search, SearchOption searchOption = SearchOption.AllDirectories); + IEnumerable EnumerateFiles( + string dir, string search, SearchOption searchOption = SearchOption.AllDirectories); + + IEnumerable EnumerateDirectories( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories); + + IEnumerable EnumerateFilesAndDirectories( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories); void Copy(string source, string dest, bool overwrite); + void CopyDirectory(string source, string dest, bool overwrite); + bool DirectoryExists(string path); void CreateDirectory(string path, bool hidden = false); @@ -21,11 +30,13 @@ public interface IFileSystem string[] ReadFileLines(string path); DateTime GetLastWriteTime(string file); - + bool IsPathRooted(string path); string GetFullPath(string path); + string TempPath { get; } + string CurrentDirectory { get; set; } string NewLine { get; } @@ -34,6 +45,8 @@ public interface IFileSystem void Move(string source, string dest); + void MoveDirectory(string source, string dest); + bool FileExists(string path); void FileDelete(string path); @@ -41,11 +54,25 @@ public interface IFileSystem IEnumerable SplitLines(string value); void WriteToFile(string path, string text); - + Stream CreateFileStream(string filePath, FileMode mode); void WriteAllBytes(string filePath, byte[] bytes); - string ModulesFolder { get; } + string GlobalFolder { get; } + + string HostBin { get; } + + string BinFolder { get; } + + string DllCacheFolder { get; } + + string PackagesFile { get; } + + string PackagesFolder { get; } + + string NugetFile { get; } + + string GlobalOptsFile { get; } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Contracts/IInstallationProvider.cs b/src/ScriptCs.Contracts/IInstallationProvider.cs index 8ff3b42a..db019ba9 100644 --- a/src/ScriptCs.Contracts/IInstallationProvider.cs +++ b/src/ScriptCs.Contracts/IInstallationProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace ScriptCs.Contracts { @@ -8,6 +7,6 @@ public interface IInstallationProvider IEnumerable GetRepositorySources(string path); void Initialize(); bool IsInstalled(IPackageReference packageId, bool allowPreRelease = false); - bool InstallPackage(IPackageReference packageId, bool allowPreRelease = false); + void InstallPackage(IPackageReference packageId, bool allowPreRelease = false); } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/ILog.cs b/src/ScriptCs.Contracts/ILog.cs new file mode 100644 index 00000000..795e61b2 --- /dev/null +++ b/src/ScriptCs.Contracts/ILog.cs @@ -0,0 +1,36 @@ +namespace ScriptCs.Contracts +{ + using System; + + public interface ILog + { + /// + /// Logs a message for a specified log level, if the is enabled for that log level. + /// + /// The log level. + /// + /// Optional function which creates the message. + /// Specify null to simply check if the logger is enabled for the . + /// + /// An optional exception to include with the message. + /// + /// Optional arguments for formatting the message created by . + /// + /// + /// true if the is enabled for the . + /// Otherwise false. + /// + /// + /// Note to implementers for optimal performance: + /// When the is null, simply return a + /// indicating whether the is enabled for the specified . + /// When the is not null, it should only be invoked + /// if the is enabled for the specified . + /// + bool Log( + LogLevel logLevel, + Func createMessage = null, + Exception exception = null, + params object[] formatArgs); + } +} diff --git a/src/ScriptCs.Contracts/ILogProvider.cs b/src/ScriptCs.Contracts/ILogProvider.cs new file mode 100644 index 00000000..17946070 --- /dev/null +++ b/src/ScriptCs.Contracts/ILogProvider.cs @@ -0,0 +1,21 @@ +namespace ScriptCs.Contracts +{ + using System; + + public interface ILogProvider + { + /// + /// Gets the specified named logger. + /// + /// Name of the logger. + /// The logger reference. + /// + /// Do not call this method directly. Instead, call a method on . + /// + Logger GetLogger(string name); + + IDisposable OpenNestedContext(string message); + + IDisposable OpenMappedContext(string key, string value); + } +} diff --git a/src/ScriptCs.Contracts/IModuleConfiguration.cs b/src/ScriptCs.Contracts/IModuleConfiguration.cs index e51f3454..49a31364 100644 --- a/src/ScriptCs.Contracts/IModuleConfiguration.cs +++ b/src/ScriptCs.Contracts/IModuleConfiguration.cs @@ -1,4 +1,7 @@ -namespace ScriptCs.Contracts +using System; +using System.Collections.Generic; + +namespace ScriptCs.Contracts { public interface IModuleConfiguration : IServiceOverrides { @@ -6,8 +9,12 @@ public interface IModuleConfiguration : IServiceOverrides string ScriptName { get; } - bool Repl { get; } + bool IsRepl { get; } LogLevel LogLevel { get; } + + bool Debug { get; } + + IDictionary Overrides { get; } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Contracts/IModuleMetadata.cs b/src/ScriptCs.Contracts/IModuleMetadata.cs index 932fc812..3ebb5b85 100644 --- a/src/ScriptCs.Contracts/IModuleMetadata.cs +++ b/src/ScriptCs.Contracts/IModuleMetadata.cs @@ -5,5 +5,7 @@ public interface IModuleMetadata string Name { get; } string Extensions { get; } + + bool Autoload { get; } } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IPackageContainer.cs b/src/ScriptCs.Contracts/IPackageContainer.cs index d52579d4..df9202e0 100644 --- a/src/ScriptCs.Contracts/IPackageContainer.cs +++ b/src/ScriptCs.Contracts/IPackageContainer.cs @@ -4,7 +4,7 @@ namespace ScriptCs.Contracts { public interface IPackageContainer { - IEnumerable CreatePackageFile(); + void CreatePackageFile(); IEnumerable FindReferences(string path); diff --git a/src/ScriptCs.Contracts/IPackageObject.cs b/src/ScriptCs.Contracts/IPackageObject.cs index 39909809..abdb940c 100644 --- a/src/ScriptCs.Contracts/IPackageObject.cs +++ b/src/ScriptCs.Contracts/IPackageObject.cs @@ -19,5 +19,9 @@ public interface IPackageObject FrameworkName FrameworkName { get; } IEnumerable Dependencies { get; } + + IEnumerable FrameworkAssemblies { get; } + + IEnumerable GetContentFiles(); } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IRepl.cs b/src/ScriptCs.Contracts/IRepl.cs new file mode 100644 index 00000000..7edcd1b4 --- /dev/null +++ b/src/ScriptCs.Contracts/IRepl.cs @@ -0,0 +1,11 @@ +namespace ScriptCs.Contracts +{ + using System.Collections.Generic; + + public interface IRepl : IScriptExecutor + { + Dictionary Commands { get; } + + string Buffer { get; } + } +} diff --git a/src/ScriptCs.Contracts/IReplCommand.cs b/src/ScriptCs.Contracts/IReplCommand.cs new file mode 100644 index 00000000..71028f3e --- /dev/null +++ b/src/ScriptCs.Contracts/IReplCommand.cs @@ -0,0 +1,11 @@ +namespace ScriptCs.Contracts +{ + public interface IReplCommand + { + string Description { get; } + + string CommandName { get; } + + object Execute(IRepl repl, object[] args); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IReplEngine.cs b/src/ScriptCs.Contracts/IReplEngine.cs new file mode 100644 index 00000000..7363fa72 --- /dev/null +++ b/src/ScriptCs.Contracts/IReplEngine.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace ScriptCs.Contracts +{ + public interface IReplEngine : IScriptEngine + { + ICollection GetLocalVariables(ScriptPackSession scriptPackSession); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IScriptEngine.cs b/src/ScriptCs.Contracts/IScriptEngine.cs index c941d613..f54b44fa 100644 --- a/src/ScriptCs.Contracts/IScriptEngine.cs +++ b/src/ScriptCs.Contracts/IScriptEngine.cs @@ -7,9 +7,14 @@ public interface IScriptEngine string BaseDirectory { get; set; } string CacheDirectory { get; set; } - + string FileName { get; set; } - - ScriptResult Execute(string code, string[] scriptArgs, AssemblyReferences references, IEnumerable namespaces, ScriptPackSession scriptPackSession); + + ScriptResult Execute( + string code, + string[] scriptArgs, + AssemblyReferences references, + IEnumerable namespaces, + ScriptPackSession scriptPackSession); } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IScriptEnvironment.cs b/src/ScriptCs.Contracts/IScriptEnvironment.cs new file mode 100644 index 00000000..60a6c637 --- /dev/null +++ b/src/ScriptCs.Contracts/IScriptEnvironment.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace ScriptCs.Contracts +{ + public interface IScriptEnvironment + { + IReadOnlyList ScriptArgs { get; } + void AddCustomPrinter(Func printer); + void Print(T o); + void Print(object o); + string ScriptPath { get; } + string[] LoadedScripts { get; } + Assembly ScriptAssembly { get; } + void Initialize(); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IScriptExecutor.cs b/src/ScriptCs.Contracts/IScriptExecutor.cs index 25eed2b9..fe9b992d 100644 --- a/src/ScriptCs.Contracts/IScriptExecutor.cs +++ b/src/ScriptCs.Contracts/IScriptExecutor.cs @@ -5,13 +5,26 @@ namespace ScriptCs.Contracts { public interface IScriptExecutor { + AssemblyReferences References { get; } + + IReadOnlyCollection Namespaces { get; } + + IScriptEngine ScriptEngine { get; } + + IFileSystem FileSystem { get; } + + ScriptPackSession ScriptPackSession { get; } + void ImportNamespaces(params string[] namespaces); + void RemoveNamespaces(params string[] namespaces); void AddReferences(params Assembly[] references); + void RemoveReferences(params Assembly[] references); void AddReferences(params string[] references); + void RemoveReferences(params string[] references); void Initialize(IEnumerable paths, IEnumerable scriptPacks, params string[] scriptArgs); diff --git a/src/ScriptCs.Contracts/IScriptHost.cs b/src/ScriptCs.Contracts/IScriptHost.cs index 45a88046..ac54c15f 100644 --- a/src/ScriptCs.Contracts/IScriptHost.cs +++ b/src/ScriptCs.Contracts/IScriptHost.cs @@ -1,6 +1,10 @@ -namespace ScriptCs.Contracts +using ScriptCs.Contracts; + +namespace ScriptCs.Contracts { public interface IScriptHost { + T Require() where T : IScriptPackContext; + IScriptEnvironment Env { get; } } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/IScriptInfo.cs b/src/ScriptCs.Contracts/IScriptInfo.cs new file mode 100644 index 00000000..1686a1af --- /dev/null +++ b/src/ScriptCs.Contracts/IScriptInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ScriptCs.Contracts +{ + public interface IScriptInfo + { + string ScriptPath { get; set; } + IList LoadedScripts { get; } + } +} diff --git a/src/ScriptCs.Contracts/IScriptLibraryComposer.cs b/src/ScriptCs.Contracts/IScriptLibraryComposer.cs new file mode 100644 index 00000000..7708af47 --- /dev/null +++ b/src/ScriptCs.Contracts/IScriptLibraryComposer.cs @@ -0,0 +1,11 @@ +using System.Text; + +namespace ScriptCs.Contracts +{ + public interface IScriptLibraryComposer + { + void Compose(string workingDirectory, StringBuilder builder = null); + + string ScriptLibrariesFile { get; } + } +} diff --git a/src/ScriptCs.Contracts/IServiceOverrides.cs b/src/ScriptCs.Contracts/IServiceOverrides.cs index 74382481..3766ed45 100644 --- a/src/ScriptCs.Contracts/IServiceOverrides.cs +++ b/src/ScriptCs.Contracts/IServiceOverrides.cs @@ -6,8 +6,12 @@ public interface IServiceOverrides : IServiceOverrides where TConfi { TConfig ScriptExecutor() where T : IScriptExecutor; + TConfig Repl() where T : IRepl; + TConfig ScriptEngine() where T : IScriptEngine; + TConfig ScriptHostFactory() where T : IScriptHostFactory; + TConfig ScriptPackManager() where T : IScriptPackManager; TConfig ScriptPackResolver() where T : IScriptPackResolver; @@ -33,7 +37,5 @@ public interface IServiceOverrides : IServiceOverrides where TConfi TConfig LineProcessor() where T : ILineProcessor; TConfig Console() where T : IConsole; - - TConfig ScriptHostFactory() where T : IScriptHostFactory; } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Contracts/Internal/LibLog.cs b/src/ScriptCs.Contracts/Internal/LibLog.cs new file mode 100644 index 00000000..f5f232ac --- /dev/null +++ b/src/ScriptCs.Contracts/Internal/LibLog.cs @@ -0,0 +1,2050 @@ +//=============================================================================== +// LibLog +// +// https://github.com/damianh/LibLog +//=============================================================================== +// Copyright © 2011-2015 Damian Hickey. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//=============================================================================== + +// ReSharper disable PossibleNullReferenceException + +// Define LIBLOG_PORTABLE conditional compilation symbol for PCL compatibility +// +// Define LIBLOG_PUBLIC to enable ability to GET a logger (LogProvider.For<>() etc) from outside this library. NOTE: +// this can have unintended consequences of consumers of your library using your library to resolve a logger. If the +// reason is because you want to open this functionality to other projects within your solution, +// consider [InternalsVisibleTo] instead. +// +// Define LIBLOG_PROVIDERS_ONLY if your library provides its own logging API and you just want to use the +// LibLog providers internally to provide built in support for popular logging frameworks. + +#pragma warning disable 1591 + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "ScriptCs.Contracts.Logging")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "ScriptCs.Contracts.Logging.Logger.#Invoke(ScriptCs.Contracts.Logging.LogLevel,System.Func`1,System.Exception,System.Object[])")] + +// If you copied this file manually, you need to change all "YourRootNameSpace" so not to clash with other libraries +// that use LibLog +#if LIBLOG_PROVIDERS_ONLY +namespace ScriptCs.Contracts.LibLog +#else +namespace ScriptCs.Contracts.Logging +#endif +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; +#if LIBLOG_PROVIDERS_ONLY + using ScriptCs.Contracts.LibLog.LogProviders; +#else + using ScriptCs.Contracts.Logging.LogProviders; +#endif + using System; +#if !LIBLOG_PROVIDERS_ONLY + using System.Diagnostics; +#if !LIBLOG_PORTABLE + using System.Runtime.CompilerServices; +#endif +#endif + +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + delegate bool Logger(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters); + +#if !LIBLOG_PROVIDERS_ONLY + /// + /// Simple interface that represent a logger. + /// +#if LIBLOG_PUBLIC + public +#else + internal +#endif + interface ILog + { + /// + /// Log a message the specified log level. + /// + /// The log level. + /// The message function. + /// An optional exception. + /// Optional format parameters for the message generated by the messagefunc. + /// true if the message was logged. Otherwise false. + /// + /// Note to implementers: the message func should not be called if the loglevel is not enabled + /// so as not to incur performance penalties. + /// + /// To check IsEnabled call Log with only LogLevel and check the return value, no event will be written. + /// + bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters ); + } +#endif + + /// + /// The log level. + /// +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + enum LogLevel + { + Trace, + Debug, + Info, + Warn, + Error, + Fatal + } + +#if !LIBLOG_PROVIDERS_ONLY +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static partial class LogExtensions + { + public static bool IsDebugEnabled(this ILog logger) + { + GuardAgainstNullLogger(logger); + return logger.Log(LogLevel.Debug, null); + } + + public static bool IsErrorEnabled(this ILog logger) + { + GuardAgainstNullLogger(logger); + return logger.Log(LogLevel.Error, null); + } + + public static bool IsFatalEnabled(this ILog logger) + { + GuardAgainstNullLogger(logger); + return logger.Log(LogLevel.Fatal, null); + } + + public static bool IsInfoEnabled(this ILog logger) + { + GuardAgainstNullLogger(logger); + return logger.Log(LogLevel.Info, null); + } + + public static bool IsTraceEnabled(this ILog logger) + { + GuardAgainstNullLogger(logger); + return logger.Log(LogLevel.Trace, null); + } + + public static bool IsWarnEnabled(this ILog logger) + { + GuardAgainstNullLogger(logger); + return logger.Log(LogLevel.Warn, null); + } + + public static void Debug(this ILog logger, Func messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Debug, messageFunc); + } + + public static void Debug(this ILog logger, string message) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc()); + } + } + + public static void DebugFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsDebugEnabled()) + { + logger.LogFormat(LogLevel.Debug, message, args); + } + } + + public static void DebugException(this ILog logger, string message, Exception exception) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc(), exception); + } + } + + public static void DebugException(this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc(), exception, formatParams); + } + } + + public static void Error(this ILog logger, Func messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Error, messageFunc); + } + + public static void Error(this ILog logger, string message) + { + if (logger.IsErrorEnabled()) + { + logger.Log(LogLevel.Error, message.AsFunc()); + } + } + + public static void ErrorFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsErrorEnabled()) + { + logger.LogFormat(LogLevel.Error, message, args); + } + } + + public static void ErrorException(this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsErrorEnabled()) + { + logger.Log(LogLevel.Error, message.AsFunc(), exception, formatParams); + } + } + + public static void Fatal(this ILog logger, Func messageFunc) + { + logger.Log(LogLevel.Fatal, messageFunc); + } + + public static void Fatal(this ILog logger, string message) + { + if (logger.IsFatalEnabled()) + { + logger.Log(LogLevel.Fatal, message.AsFunc()); + } + } + + public static void FatalFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsFatalEnabled()) + { + logger.LogFormat(LogLevel.Fatal, message, args); + } + } + + public static void FatalException(this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsFatalEnabled()) + { + logger.Log(LogLevel.Fatal, message.AsFunc(), exception, formatParams); + } + } + + public static void Info(this ILog logger, Func messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Info, messageFunc); + } + + public static void Info(this ILog logger, string message) + { + if (logger.IsInfoEnabled()) + { + logger.Log(LogLevel.Info, message.AsFunc()); + } + } + + public static void InfoFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsInfoEnabled()) + { + logger.LogFormat(LogLevel.Info, message, args); + } + } + + public static void InfoException(this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsInfoEnabled()) + { + logger.Log(LogLevel.Info, message.AsFunc(), exception, formatParams); + } + } + + public static void Trace(this ILog logger, Func messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Trace, messageFunc); + } + + public static void Trace(this ILog logger, string message) + { + if (logger.IsTraceEnabled()) + { + logger.Log(LogLevel.Trace, message.AsFunc()); + } + } + + public static void TraceFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsTraceEnabled()) + { + logger.LogFormat(LogLevel.Trace, message, args); + } + } + + public static void TraceException(this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsTraceEnabled()) + { + logger.Log(LogLevel.Trace, message.AsFunc(), exception, formatParams); + } + } + + public static void Warn(this ILog logger, Func messageFunc) + { + GuardAgainstNullLogger(logger); + logger.Log(LogLevel.Warn, messageFunc); + } + + public static void Warn(this ILog logger, string message) + { + if (logger.IsWarnEnabled()) + { + logger.Log(LogLevel.Warn, message.AsFunc()); + } + } + + public static void WarnFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsWarnEnabled()) + { + logger.LogFormat(LogLevel.Warn, message, args); + } + } + + public static void WarnException(this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsWarnEnabled()) + { + logger.Log(LogLevel.Warn, message.AsFunc(), exception, formatParams); + } + } + + // ReSharper disable once UnusedParameter.Local + private static void GuardAgainstNullLogger(ILog logger) + { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + } + + private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args) + { + logger.Log(logLevel, message.AsFunc(), null, args); + } + + // Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b + private static Func AsFunc(this T value) where T : class + { + return value.Return; + } + + private static T Return(this T value) + { + return value; + } + } +#endif + + /// + /// Represents a way to get a + /// +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + interface ILogProvider + { + /// + /// Gets the specified named logger. + /// + /// Name of the logger. + /// The logger reference. + Logger GetLogger(string name); + + /// + /// Opens a nested diagnostics context. Not supported in EntLib logging. + /// + /// The message to add to the diagnostics context. + /// A disposable that when disposed removes the message from the context. + IDisposable OpenNestedContext(string message); + + /// + /// Opens a mapped diagnostics context. Not supported in EntLib logging. + /// + /// A key. + /// A value. + /// A disposable that when disposed removes the map from the context. + IDisposable OpenMappedContext(string key, string value); + } + + /// + /// Provides a mechanism to create instances of objects. + /// +#if LIBLOG_PROVIDERS_ONLY + internal +#else + public +#endif + static class LogProvider + { +#if !LIBLOG_PROVIDERS_ONLY + /// + /// The disable logging environment variable. If the environment variable is set to 'true', then logging + /// will be disabled. + /// + public const string DisableLoggingEnvironmentVariable = "ScriptCs.Contracts_LIBLOG_DISABLE"; + private const string NullLogProvider = "Current Log Provider is not set. Call SetCurrentLogProvider " + + "with a non-null value first."; + private static dynamic s_currentLogProvider; + private static Action s_onCurrentLogProviderSet; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static LogProvider() + { + IsDisabled = false; + } + + /// + /// Sets the current log provider. + /// + /// The log provider. + public static void SetCurrentLogProvider(ILogProvider logProvider) + { + s_currentLogProvider = logProvider; + + RaiseOnCurrentLogProviderSet(); + } + + /// + /// Gets or sets a value indicating whether this is logging is disabled. + /// + /// + /// true if logging is disabled; otherwise, false. + /// + public static bool IsDisabled { get; set; } + + /// + /// Sets an action that is invoked when a consumer of your library has called SetCurrentLogProvider. It is + /// important that hook into this if you are using child libraries (especially ilmerged ones) that are using + /// LibLog (or other logging abstraction) so you adapt and delegate to them. + /// + /// + internal static Action OnCurrentLogProviderSet + { + set + { + s_onCurrentLogProviderSet = value; + RaiseOnCurrentLogProviderSet(); + } + } + + internal static ILogProvider CurrentLogProvider + { + get + { + return s_currentLogProvider; + } + } + + /// + /// Gets a logger for the specified type. + /// + /// The type whose name will be used for the logger. + /// An instance of +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog For() + { + return GetLogger(typeof(T)); + } + +#if !LIBLOG_PORTABLE + /// + /// Gets a logger for the current class. + /// + /// An instance of + [MethodImpl(MethodImplOptions.NoInlining)] +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog GetCurrentClassLogger() + { + var stackFrame = new StackFrame(1, false); + return GetLogger(stackFrame.GetMethod().DeclaringType); + } +#endif + + /// + /// Gets a logger for the specified type. + /// + /// The type whose name will be used for the logger. + /// An instance of +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog GetLogger(Type type) + { + return GetLogger(type.FullName); + } + + /// + /// Gets a logger with the specified name. + /// + /// The name. + /// An instance of +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static ILog GetLogger(string name) + { + ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider(); + return logProvider == null + ? NoOpLogger.Instance + : (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name), () => IsDisabled); + } + + /// + /// Opens a nested diagnostics context. + /// + /// A message. + /// An that closes context when disposed. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")] +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static IDisposable OpenNestedContext(string message) + { + return CurrentLogProvider == null + ? new DisposableAction(() => {}) + : CurrentLogProvider.OpenNestedContext(message); + } + + /// + /// Opens a mapped diagnostics context. + /// + /// A key. + /// A value. + /// An that closes context when disposed. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")] +#if LIBLOG_PUBLIC + public +#else + internal +#endif + static IDisposable OpenMappedContext(string key, string value) + { + return CurrentLogProvider == null + ? new DisposableAction(() => { }) + : CurrentLogProvider.OpenMappedContext(key, value); + } +#endif + +#if LIBLOG_PROVIDERS_ONLY + private +#else + internal +#endif + delegate bool IsLoggerAvailable(); + +#if LIBLOG_PROVIDERS_ONLY + private +#else + internal +#endif + delegate ILogProvider CreateLogProvider(); + +#if LIBLOG_PROVIDERS_ONLY + private +#else + internal +#endif + static readonly List> LogProviderResolvers = + new List> + { + new Tuple(SerilogLogProvider.IsLoggerAvailable, () => new SerilogLogProvider()), + new Tuple(NLogLogProvider.IsLoggerAvailable, () => new NLogLogProvider()), + new Tuple(Log4NetLogProvider.IsLoggerAvailable, () => new Log4NetLogProvider()), + new Tuple(EntLibLogProvider.IsLoggerAvailable, () => new EntLibLogProvider()), + new Tuple(LoupeLogProvider.IsLoggerAvailable, () => new LoupeLogProvider()), + }; + +#if !LIBLOG_PROVIDERS_ONLY + private static void RaiseOnCurrentLogProviderSet() + { + if (s_onCurrentLogProviderSet != null) + { + s_onCurrentLogProviderSet(s_currentLogProvider); + } + } +#endif + + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static ILogProvider ResolveLogProvider() + { + try + { + foreach (var providerResolver in LogProviderResolvers) + { + if (providerResolver.Item1()) + { + return providerResolver.Item2(); + } + } + } + catch (Exception ex) + { +#if LIBLOG_PORTABLE + Debug.WriteLine( +#else + Console.WriteLine( +#endif + "Exception occurred resolving a log provider. Logging for this assembly {0} is disabled. {1}", + typeof(LogProvider).GetAssemblyPortable().FullName, + ex); + } + return null; + } + +#if !LIBLOG_PROVIDERS_ONLY + internal class NoOpLogger : ILog + { + internal static readonly NoOpLogger Instance = new NoOpLogger(); + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + return false; + } + } +#endif + } + +#if !LIBLOG_PROVIDERS_ONLY + internal class LoggerExecutionWrapper : ILog + { + private readonly Logger _logger; + private readonly Func _getIsDisabled; + internal const string FailedToGenerateLogMessage = "Failed to generate log message"; + + internal LoggerExecutionWrapper(Logger logger, Func getIsDisabled = null) + { + _logger = logger; + _getIsDisabled = getIsDisabled ?? (() => false); + } + + internal Logger WrappedLogger + { + get { return _logger; } + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters) + { + if (_getIsDisabled()) + { + return false; + } +#if !LIBLOG_PORTABLE + var envVar = Environment.GetEnvironmentVariable(LogProvider.DisableLoggingEnvironmentVariable); + + if (envVar != null && envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + return false; + } +#endif + + if (messageFunc == null) + { + return _logger(logLevel, null); + } + + Func wrappedMessageFunc = () => + { + try + { + return messageFunc(); + } + catch (Exception ex) + { + Log(LogLevel.Error, () => FailedToGenerateLogMessage, ex); + } + return null; + }; + return _logger(logLevel, wrappedMessageFunc, exception, formatParameters); + } + } +#endif +} + +#if LIBLOG_PROVIDERS_ONLY +namespace ScriptCs.Contracts.LibLog.LogProviders +#else +namespace ScriptCs.Contracts.Logging.LogProviders +#endif +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; +#if !LIBLOG_PORTABLE + using System.Diagnostics; +#endif + using System.Globalization; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; +#if !LIBLOG_PORTABLE + using System.Text; +#endif + using System.Text.RegularExpressions; + + internal abstract class LogProviderBase : ILogProvider + { + protected delegate IDisposable OpenNdc(string message); + protected delegate IDisposable OpenMdc(string key, string value); + + private readonly Lazy _lazyOpenNdcMethod; + private readonly Lazy _lazyOpenMdcMethod; + private static readonly IDisposable NoopDisposableInstance = new DisposableAction(); + + protected LogProviderBase() + { + _lazyOpenNdcMethod + = new Lazy(GetOpenNdcMethod); + _lazyOpenMdcMethod + = new Lazy(GetOpenMdcMethod); + } + + public abstract Logger GetLogger(string name); + + public IDisposable OpenNestedContext(string message) + { + return _lazyOpenNdcMethod.Value(message); + } + + public IDisposable OpenMappedContext(string key, string value) + { + return _lazyOpenMdcMethod.Value(key, value); + } + + protected virtual OpenNdc GetOpenNdcMethod() + { + return _ => NoopDisposableInstance; + } + + protected virtual OpenMdc GetOpenMdcMethod() + { + return (_, __) => NoopDisposableInstance; + } + } + + internal class NLogLogProvider : LogProviderBase + { + private readonly Func _getLoggerByNameDelegate; + private static bool s_providerIsAvailableOverride = true; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "NLog")] + public NLogLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("NLog.LogManager not found"); + } + _getLoggerByNameDelegate = GetGetLoggerMethodCall(); + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new NLogLogger(_getLoggerByNameDelegate(name)).Log; + } + + public static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + protected override OpenNdc GetOpenNdcMethod() + { + Type ndcContextType = Type.GetType("NLog.NestedDiagnosticsContext, NLog"); + MethodInfo pushMethod = ndcContextType.GetMethodPortable("Push", typeof(string)); + ParameterExpression messageParam = Expression.Parameter(typeof(string), "message"); + MethodCallExpression pushMethodCall = Expression.Call(null, pushMethod, messageParam); + return Expression.Lambda(pushMethodCall, messageParam).Compile(); + } + + protected override OpenMdc GetOpenMdcMethod() + { + Type mdcContextType = Type.GetType("NLog.MappedDiagnosticsContext, NLog"); + + MethodInfo setMethod = mdcContextType.GetMethodPortable("Set", typeof(string), typeof(string)); + MethodInfo removeMethod = mdcContextType.GetMethodPortable("Remove", typeof(string)); + ParameterExpression keyParam = Expression.Parameter(typeof(string), "key"); + ParameterExpression valueParam = Expression.Parameter(typeof(string), "value"); + + MethodCallExpression setMethodCall = Expression.Call(null, setMethod, keyParam, valueParam); + MethodCallExpression removeMethodCall = Expression.Call(null, removeMethod, keyParam); + + Action set = Expression + .Lambda>(setMethodCall, keyParam, valueParam) + .Compile(); + Action remove = Expression + .Lambda>(removeMethodCall, keyParam) + .Compile(); + + return (key, value) => + { + set(key, value); + return new DisposableAction(() => remove(key)); + }; + } + + private static Type GetLogManagerType() + { + return Type.GetType("NLog.LogManager, NLog"); + } + + private static Func GetGetLoggerMethodCall() + { + Type logManagerType = GetLogManagerType(); + MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string)); + ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); + MethodCallExpression methodCall = Expression.Call(null, method, nameParam); + return Expression.Lambda>(methodCall, nameParam).Compile(); + } + + internal class NLogLogger + { + private readonly dynamic _logger; + + internal NLogLogger(dynamic logger) + { + _logger = logger; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsLogLevelEnable(logLevel); + } + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + if(exception != null) + { + return LogException(logLevel, messageFunc, exception); + } + switch (logLevel) + { + case LogLevel.Debug: + if (_logger.IsDebugEnabled) + { + _logger.Debug(messageFunc()); + return true; + } + break; + case LogLevel.Info: + if (_logger.IsInfoEnabled) + { + _logger.Info(messageFunc()); + return true; + } + break; + case LogLevel.Warn: + if (_logger.IsWarnEnabled) + { + _logger.Warn(messageFunc()); + return true; + } + break; + case LogLevel.Error: + if (_logger.IsErrorEnabled) + { + _logger.Error(messageFunc()); + return true; + } + break; + case LogLevel.Fatal: + if (_logger.IsFatalEnabled) + { + _logger.Fatal(messageFunc()); + return true; + } + break; + default: + if (_logger.IsTraceEnabled) + { + _logger.Trace(messageFunc()); + return true; + } + break; + } + return false; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) + { + switch (logLevel) + { + case LogLevel.Debug: + if (_logger.IsDebugEnabled) + { + _logger.DebugException(messageFunc(), exception); + return true; + } + break; + case LogLevel.Info: + if (_logger.IsInfoEnabled) + { + _logger.InfoException(messageFunc(), exception); + return true; + } + break; + case LogLevel.Warn: + if (_logger.IsWarnEnabled) + { + _logger.WarnException(messageFunc(), exception); + return true; + } + break; + case LogLevel.Error: + if (_logger.IsErrorEnabled) + { + _logger.ErrorException(messageFunc(), exception); + return true; + } + break; + case LogLevel.Fatal: + if (_logger.IsFatalEnabled) + { + _logger.FatalException(messageFunc(), exception); + return true; + } + break; + default: + if (_logger.IsTraceEnabled) + { + _logger.TraceException(messageFunc(), exception); + return true; + } + break; + } + return false; + } + + private bool IsLogLevelEnable(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Debug: + return _logger.IsDebugEnabled; + case LogLevel.Info: + return _logger.IsInfoEnabled; + case LogLevel.Warn: + return _logger.IsWarnEnabled; + case LogLevel.Error: + return _logger.IsErrorEnabled; + case LogLevel.Fatal: + return _logger.IsFatalEnabled; + default: + return _logger.IsTraceEnabled; + } + } + } + } + + internal class Log4NetLogProvider : LogProviderBase + { + private readonly Func _getLoggerByNameDelegate; + private static bool s_providerIsAvailableOverride = true; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")] + public Log4NetLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("log4net.LogManager not found"); + } + _getLoggerByNameDelegate = GetGetLoggerMethodCall(); + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new Log4NetLogger(_getLoggerByNameDelegate(name)).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + protected override OpenNdc GetOpenNdcMethod() + { + Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net"); + PropertyInfo stacksProperty = logicalThreadContextType.GetPropertyPortable("Stacks"); + Type logicalThreadContextStacksType = stacksProperty.PropertyType; + PropertyInfo stacksIndexerProperty = logicalThreadContextStacksType.GetPropertyPortable("Item"); + Type stackType = stacksIndexerProperty.PropertyType; + MethodInfo pushMethod = stackType.GetMethodPortable("Push"); + + ParameterExpression messageParameter = + Expression.Parameter(typeof(string), "message"); + + // message => LogicalThreadContext.Stacks.Item["NDC"].Push(message); + MethodCallExpression callPushBody = + Expression.Call( + Expression.Property(Expression.Property(null, stacksProperty), + stacksIndexerProperty, + Expression.Constant("NDC")), + pushMethod, + messageParameter); + + OpenNdc result = + Expression.Lambda(callPushBody, messageParameter) + .Compile(); + + return result; + } + + protected override OpenMdc GetOpenMdcMethod() + { + Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net"); + PropertyInfo propertiesProperty = logicalThreadContextType.GetPropertyPortable("Properties"); + Type logicalThreadContextPropertiesType = propertiesProperty.PropertyType; + PropertyInfo propertiesIndexerProperty = logicalThreadContextPropertiesType.GetPropertyPortable("Item"); + + MethodInfo removeMethod = logicalThreadContextPropertiesType.GetMethodPortable("Remove"); + + ParameterExpression keyParam = Expression.Parameter(typeof(string), "key"); + ParameterExpression valueParam = Expression.Parameter(typeof(string), "value"); + + MemberExpression propertiesExpression = Expression.Property(null, propertiesProperty); + + // (key, value) => LogicalThreadContext.Properties.Item[key] = value; + BinaryExpression setProperties = Expression.Assign(Expression.Property(propertiesExpression, propertiesIndexerProperty, keyParam), valueParam); + + // key => LogicalThreadContext.Properties.Remove(key); + MethodCallExpression removeMethodCall = Expression.Call(propertiesExpression, removeMethod, keyParam); + + Action set = Expression + .Lambda>(setProperties, keyParam, valueParam) + .Compile(); + + Action remove = Expression + .Lambda>(removeMethodCall, keyParam) + .Compile(); + + return (key, value) => + { + set(key, value); + return new DisposableAction(() => remove(key)); + }; + } + + private static Type GetLogManagerType() + { + return Type.GetType("log4net.LogManager, log4net"); + } + + private static Func GetGetLoggerMethodCall() + { + Type logManagerType = GetLogManagerType(); + MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string)); + ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); + MethodCallExpression methodCall = Expression.Call(null, method, nameParam); + return Expression.Lambda>(methodCall, nameParam).Compile(); + } + + internal class Log4NetLogger + { + private readonly dynamic _logger; + private static Type s_callerStackBoundaryType; + private static readonly object CallerStackBoundaryTypeSync = new object(); + + private readonly object _levelDebug; + private readonly object _levelInfo; + private readonly object _levelWarn; + private readonly object _levelError; + private readonly object _levelFatal; + private readonly Func _isEnabledForDelegate; + private readonly Action _logDelegate; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + internal Log4NetLogger(dynamic logger) + { + _logger = logger.Logger; + + var logEventLevelType = Type.GetType("log4net.Core.Level, log4net"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type log4net.Core.Level was not found."); + } + + var levelFields = logEventLevelType.GetFieldsPortable().ToList(); + _levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null); + _levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null); + _levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null); + _levelError = levelFields.First(x => x.Name == "Error").GetValue(null); + _levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null); + + // Func isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("log4net.Core.ILogger, log4net"); + if (loggerType == null) + { + throw new InvalidOperationException("Type log4net.Core.ILogger, was not found."); + } + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + _isEnabledForDelegate = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + + // Action Log = + // (logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Write(callerStackBoundaryDeclaringType, level, message, exception); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log", + typeof(Type), + logEventLevelType, + typeof(string), + typeof(Exception)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + var writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + callerStackBoundaryDeclaringTypeParam, + levelCast, + messageParam, + exceptionParam); + _logDelegate = Expression.Lambda>( + writeMethodExp, + instanceParam, + callerStackBoundaryDeclaringTypeParam, + levelParam, + messageParam, + exceptionParam).Compile(); + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsLogLevelEnable(logLevel); + } + + if (!IsLogLevelEnable(logLevel)) + { + return false; + } + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + // determine correct caller - this might change due to jit optimizations with method inlining + if (s_callerStackBoundaryType == null) + { + lock (CallerStackBoundaryTypeSync) + { +#if !LIBLOG_PORTABLE + StackTrace stack = new StackTrace(); + Type thisType = GetType(); + s_callerStackBoundaryType = Type.GetType("LoggerExecutionWrapper"); + for (var i = 1; i < stack.FrameCount; i++) + { + if (!IsInTypeHierarchy(thisType, stack.GetFrame(i).GetMethod().DeclaringType)) + { + s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType; + break; + } + } +#else + s_callerStackBoundaryType = typeof (LoggerExecutionWrapper); +#endif + } + } + + var translatedLevel = TranslateLevel(logLevel); + _logDelegate(_logger, s_callerStackBoundaryType, translatedLevel, messageFunc(), exception); + return true; + } + + private static bool IsInTypeHierarchy(Type currentType, Type checkType) + { + while (currentType != null && currentType != typeof(object)) + { + if (currentType == checkType) + { + return true; + } + currentType = currentType.GetBaseTypePortable(); + } + return false; + } + + private bool IsLogLevelEnable(LogLevel logLevel) + { + var level = TranslateLevel(logLevel); + return _isEnabledForDelegate(_logger, level); + } + + private object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + case LogLevel.Debug: + return _levelDebug; + case LogLevel.Info: + return _levelInfo; + case LogLevel.Warn: + return _levelWarn; + case LogLevel.Error: + return _levelError; + case LogLevel.Fatal: + return _levelFatal; + default: + throw new ArgumentOutOfRangeException("logLevel", logLevel, null); + } + } + } + } + + internal class EntLibLogProvider : LogProviderBase + { + private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging"; + private static bool s_providerIsAvailableOverride = true; + private static readonly Type LogEntryType; + private static readonly Type LoggerType; + private static readonly Type TraceEventTypeType; + private static readonly Action WriteLogEntry; + private static readonly Func ShouldLogEntry; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static EntLibLogProvider() + { + LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")); + LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger")); + TraceEventTypeType = TraceEventTypeValues.Type; + if (LogEntryType == null + || TraceEventTypeType == null + || LoggerType == null) + { + return; + } + WriteLogEntry = GetWriteLogEntry(); + ShouldLogEntry = GetShouldLogEntry(); + } + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnterpriseLibrary")] + public EntLibLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found"); + } + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride + && TraceEventTypeType != null + && LogEntryType != null; + } + + private static Action GetWriteLogEntry() + { + // new LogEntry(...) + var logNameParameter = Expression.Parameter(typeof(string), "logName"); + var messageParameter = Expression.Parameter(typeof(string), "message"); + var severityParameter = Expression.Parameter(typeof(int), "severity"); + + MemberInitExpression memberInit = GetWriteLogExpression( + messageParameter, + Expression.Convert(severityParameter, TraceEventTypeType), + logNameParameter); + + //Logger.Write(new LogEntry(....)); + MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("Write", LogEntryType); + var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); + + return Expression.Lambda>( + writeLogEntryExpression, + logNameParameter, + messageParameter, + severityParameter).Compile(); + } + + private static Func GetShouldLogEntry() + { + // new LogEntry(...) + var logNameParameter = Expression.Parameter(typeof(string), "logName"); + var severityParameter = Expression.Parameter(typeof(int), "severity"); + + MemberInitExpression memberInit = GetWriteLogExpression( + Expression.Constant("***dummy***"), + Expression.Convert(severityParameter, TraceEventTypeType), + logNameParameter); + + //Logger.Write(new LogEntry(....)); + MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("ShouldLog", LogEntryType); + var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); + + return Expression.Lambda>( + writeLogEntryExpression, + logNameParameter, + severityParameter).Compile(); + } + + private static MemberInitExpression GetWriteLogExpression(Expression message, + Expression severityParameter, ParameterExpression logNameParameter) + { + var entryType = LogEntryType; + MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), + Expression.Bind(entryType.GetPropertyPortable("Message"), message), + Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter), + Expression.Bind( + entryType.GetPropertyPortable("TimeStamp"), + Expression.Property(null, typeof (DateTime).GetPropertyPortable("UtcNow"))), + Expression.Bind( + entryType.GetPropertyPortable("Categories"), + Expression.ListInit( + Expression.New(typeof (List)), + typeof (List).GetMethodPortable("Add", typeof (string)), + logNameParameter))); + return memberInit; + } + + internal class EntLibLogger + { + private readonly string _loggerName; + private readonly Action _writeLog; + private readonly Func _shouldLog; + + internal EntLibLogger(string loggerName, Action writeLog, Func shouldLog) + { + _loggerName = loggerName; + _writeLog = writeLog; + _shouldLog = shouldLog; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + var severity = MapSeverity(logLevel); + if (messageFunc == null) + { + return _shouldLog(_loggerName, severity); + } + + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + if (exception != null) + { + return LogException(logLevel, messageFunc, exception); + } + _writeLog(_loggerName, messageFunc(), severity); + return true; + } + + public bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) + { + var severity = MapSeverity(logLevel); + var message = messageFunc() + Environment.NewLine + exception; + _writeLog(_loggerName, message, severity); + return true; + } + + private static int MapSeverity(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Fatal: + return TraceEventTypeValues.Critical; + case LogLevel.Error: + return TraceEventTypeValues.Error; + case LogLevel.Warn: + return TraceEventTypeValues.Warning; + case LogLevel.Info: + return TraceEventTypeValues.Information; + default: + return TraceEventTypeValues.Verbose; + } + } + } + } + + internal class SerilogLogProvider : LogProviderBase + { + private readonly Func _getLoggerByNameDelegate; + private static bool s_providerIsAvailableOverride = true; + + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + public SerilogLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Serilog.Log not found"); + } + _getLoggerByNameDelegate = GetForContextMethodCall(); + } + + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new SerilogLogger(_getLoggerByNameDelegate(name)).Log; + } + + internal static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + protected override OpenNdc GetOpenNdcMethod() + { + return message => GetPushProperty()("NDC", message); + } + + protected override OpenMdc GetOpenMdcMethod() + { + return (key, value) => GetPushProperty()(key, value); + } + + private static Func GetPushProperty() + { + Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); + MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable( + "PushProperty", + typeof(string), + typeof(object), + typeof(bool)); + ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); + ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); + ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects"); + MethodCallExpression pushPropertyMethodCall = Expression + .Call(null, pushPropertyMethod, nameParam, valueParam, destructureObjectParam); + var pushProperty = Expression + .Lambda>( + pushPropertyMethodCall, + nameParam, + valueParam, + destructureObjectParam) + .Compile(); + + return (key, value) => pushProperty(key, value, false); + } + + private static Type GetLogManagerType() + { + return Type.GetType("Serilog.Log, Serilog"); + } + + private static Func GetForContextMethodCall() + { + Type logManagerType = GetLogManagerType(); + MethodInfo method = logManagerType.GetMethodPortable("ForContext", typeof(string), typeof(object), typeof(bool)); + ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName"); + ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); + ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects"); + MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] + { + propertyNameParam, + valueParam, + destructureObjectsParam + }); + var func = Expression.Lambda>( + methodCall, + propertyNameParam, + valueParam, + destructureObjectsParam) + .Compile(); + return name => func("Name", name, false); + } + + internal class SerilogLogger + { + private readonly object _logger; + private static readonly object DebugLevel; + private static readonly object ErrorLevel; + private static readonly object FatalLevel; + private static readonly object InformationLevel; + private static readonly object VerboseLevel; + private static readonly object WarningLevel; + private static readonly Func IsEnabled; + private static readonly Action Write; + private static readonly Action WriteException; + + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogEventLevel")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")] + static SerilogLogger() + { + var logEventLevelType = Type.GetType("Serilog.Events.LogEventLevel, Serilog"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type Serilog.Events.LogEventLevel was not found."); + } + DebugLevel = Enum.Parse(logEventLevelType, "Debug", false); + ErrorLevel = Enum.Parse(logEventLevelType, "Error", false); + FatalLevel = Enum.Parse(logEventLevelType, "Fatal", false); + InformationLevel = Enum.Parse(logEventLevelType, "Information", false); + VerboseLevel = Enum.Parse(logEventLevelType, "Verbose", false); + WarningLevel = Enum.Parse(logEventLevelType, "Warning", false); + + // Func isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); } + var loggerType = Type.GetType("Serilog.ILogger, Serilog"); + if (loggerType == null) + { + throw new InvalidOperationException("Type Serilog.ILogger was not found."); + } + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabled", logEventLevelType); + ParameterExpression instanceParam = Expression.Parameter(typeof(object)); + UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + IsEnabled = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + + // Action Write = + // (logger, level, message, params) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, params); } + MethodInfo writeMethodInfo = loggerType.GetMethodPortable("Write", logEventLevelType, typeof(string), typeof(object[])); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression propertyValuesParam = Expression.Parameter(typeof(object[])); + MethodCallExpression writeMethodExp = Expression.Call( + instanceCast, + writeMethodInfo, + levelCast, + messageParam, + propertyValuesParam); + var expression = Expression.Lambda>( + writeMethodExp, + instanceParam, + levelParam, + messageParam, + propertyValuesParam); + Write = expression.Compile(); + + // Action WriteException = + // (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); } + MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Write", + logEventLevelType, + typeof(Exception), + typeof(string), + typeof(object[])); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + writeMethodExp = Expression.Call( + instanceCast, + writeExceptionMethodInfo, + levelCast, + exceptionParam, + messageParam, + propertyValuesParam); + WriteException = Expression.Lambda>( + writeMethodExp, + instanceParam, + levelParam, + exceptionParam, + messageParam, + propertyValuesParam).Compile(); + } + + internal SerilogLogger(object logger) + { + _logger = logger; + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + return IsEnabled(_logger, logLevel); + } + if (exception != null) + { + return LogException(logLevel, messageFunc, exception, formatParameters); + } + + switch (logLevel) + { + case LogLevel.Debug: + if (IsEnabled(_logger, DebugLevel)) + { + Write(_logger, DebugLevel, messageFunc(), formatParameters); + return true; + } + break; + case LogLevel.Info: + if (IsEnabled(_logger, InformationLevel)) + { + Write(_logger, InformationLevel, messageFunc(), formatParameters); + return true; + } + break; + case LogLevel.Warn: + if (IsEnabled(_logger, WarningLevel)) + { + Write(_logger, WarningLevel, messageFunc(), formatParameters); + return true; + } + break; + case LogLevel.Error: + if (IsEnabled(_logger, ErrorLevel)) + { + Write(_logger, ErrorLevel, messageFunc(), formatParameters); + return true; + } + break; + case LogLevel.Fatal: + if (IsEnabled(_logger, FatalLevel)) + { + Write(_logger, FatalLevel, messageFunc(), formatParameters); + return true; + } + break; + default: + if (IsEnabled(_logger, VerboseLevel)) + { + Write(_logger, VerboseLevel, messageFunc(), formatParameters); + return true; + } + break; + } + return false; + } + + private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception, object[] formatParams) + { + switch (logLevel) + { + case LogLevel.Debug: + if (IsEnabled(_logger, DebugLevel)) + { + WriteException(_logger, DebugLevel, exception, messageFunc(), formatParams); + return true; + } + break; + case LogLevel.Info: + if (IsEnabled(_logger, InformationLevel)) + { + WriteException(_logger, InformationLevel, exception, messageFunc(), formatParams); + return true; + } + break; + case LogLevel.Warn: + if (IsEnabled(_logger, WarningLevel)) + { + WriteException(_logger, WarningLevel, exception, messageFunc(), formatParams); + return true; + } + break; + case LogLevel.Error: + if (IsEnabled(_logger, ErrorLevel)) + { + WriteException(_logger, ErrorLevel, exception, messageFunc(), formatParams); + return true; + } + break; + case LogLevel.Fatal: + if (IsEnabled(_logger, FatalLevel)) + { + WriteException(_logger, FatalLevel, exception, messageFunc(), formatParams); + return true; + } + break; + default: + if (IsEnabled(_logger, VerboseLevel)) + { + WriteException(_logger, VerboseLevel, exception, messageFunc(), formatParams); + return true; + } + break; + } + return false; + } + } + } + + internal class LoupeLogProvider : LogProviderBase + { + /// + /// The form of the Loupe Log.Write method we're using + /// + internal delegate void WriteDelegate( + int severity, + string logSystem, + int skipFrames, + Exception exception, + bool attributeToException, + int writeMode, + string detailsXml, + string category, + string caption, + string description, + params object[] args + ); + + private static bool s_providerIsAvailableOverride = true; + private readonly WriteDelegate _logWriteDelegate; + + public LoupeLogProvider() + { + if (!IsLoggerAvailable()) + { + throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found"); + } + + _logWriteDelegate = GetLogWriteDelegate(); + } + + /// + /// Gets or sets a value indicating whether [provider is available override]. Used in tests. + /// + /// + /// true if [provider is available override]; otherwise, false. + /// + public static bool ProviderIsAvailableOverride + { + get { return s_providerIsAvailableOverride; } + set { s_providerIsAvailableOverride = value; } + } + + public override Logger GetLogger(string name) + { + return new LoupeLogger(name, _logWriteDelegate).Log; + } + + public static bool IsLoggerAvailable() + { + return ProviderIsAvailableOverride && GetLogManagerType() != null; + } + + private static Type GetLogManagerType() + { + return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent"); + } + + private static WriteDelegate GetLogWriteDelegate() + { + Type logManagerType = GetLogManagerType(); + Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent"); + Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent"); + + MethodInfo method = logManagerType.GetMethodPortable( + "Write", + logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool), + logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[])); + + var callDelegate = (WriteDelegate)method.CreateDelegate(typeof(WriteDelegate)); + return callDelegate; + } + + internal class LoupeLogger + { + private const string LogSystem = "LibLog"; + + private readonly string _category; + private readonly WriteDelegate _logWriteDelegate; + private readonly int _skipLevel; + + internal LoupeLogger(string category, WriteDelegate logWriteDelegate) + { + _category = category; + _logWriteDelegate = logWriteDelegate; +#if DEBUG + _skipLevel = 2; +#else + _skipLevel = 1; +#endif + } + + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + if (messageFunc == null) + { + //nothing to log.. + return true; + } + + messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + + _logWriteDelegate(ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null, + _category, null, messageFunc.Invoke()); + + return true; + } + + private static int ToLogMessageSeverity(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + return TraceEventTypeValues.Verbose; + case LogLevel.Debug: + return TraceEventTypeValues.Verbose; + case LogLevel.Info: + return TraceEventTypeValues.Information; + case LogLevel.Warn: + return TraceEventTypeValues.Warning; + case LogLevel.Error: + return TraceEventTypeValues.Error; + case LogLevel.Fatal: + return TraceEventTypeValues.Critical; + default: + throw new ArgumentOutOfRangeException("logLevel"); + } + } + } + } + + internal static class TraceEventTypeValues + { + internal static readonly Type Type; + internal static readonly int Verbose; + internal static readonly int Information; + internal static readonly int Warning; + internal static readonly int Error; + internal static readonly int Critical; + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static TraceEventTypeValues() + { + var assembly = typeof(Uri).GetAssemblyPortable(); // This is to get to the System.dll assembly in a PCL compatible way. + if (assembly == null) + { + return; + } + Type = assembly.GetType("System.Diagnostics.TraceEventType"); + if (Type == null) return; + Verbose = (int)Enum.Parse(Type, "Verbose", false); + Information = (int)Enum.Parse(Type, "Information", false); + Warning = (int)Enum.Parse(Type, "Warning", false); + Error = (int)Enum.Parse(Type, "Error", false); + Critical = (int)Enum.Parse(Type, "Critical", false); + } + } + + internal static class LogMessageFormatter + { + private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); + + /// + /// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string: + /// For example: Log("Log message to {user}", user). This only works with serilog, but as the user of LibLog, you don't know if serilog is actually + /// used. So, this class simulates that. it will replace any text in {curly braces} with an index number. + /// + /// "Log {message} to {user}" would turn into => "Log {0} to {1}". Then the format parameters are handled using regular .net string.Format. + /// + /// The message builder. + /// The format parameters. + /// + public static Func SimulateStructuredLogging(Func messageBuilder, object[] formatParameters) + { + if (formatParameters == null || formatParameters.Length == 0) + { + return messageBuilder; + } + + return () => + { + string targetMessage = messageBuilder(); + int argumentIndex = 0; + foreach (Match match in Pattern.Matches(targetMessage)) + { + int notUsed; + if (!int.TryParse(match.Value.Substring(1, match.Value.Length -2), out notUsed)) + { + targetMessage = ReplaceFirst(targetMessage, match.Value, + "{" + argumentIndex++ + "}"); + } + } + try + { + return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters); + } + catch (FormatException ex) + { + throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex); + } + }; + } + + private static string ReplaceFirst(string text, string search, string replace) + { + int pos = text.IndexOf(search, StringComparison.Ordinal); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + } + + internal static class TypeExtensions + { + internal static MethodInfo GetMethodPortable(this Type type, string name) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeMethods().SingleOrDefault(m => m.Name == name); +#else + return type.GetMethod(name); +#endif + } + + internal static MethodInfo GetMethodPortable(this Type type, string name, params Type[] types) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeMethod(name, types); +#else + return type.GetMethod(name, types); +#endif + } + + internal static PropertyInfo GetPropertyPortable(this Type type, string name) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeProperty(name); +#else + return type.GetProperty(name); +#endif + } + + internal static IEnumerable GetFieldsPortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetRuntimeFields(); +#else + return type.GetFields(); +#endif + } + + internal static Type GetBaseTypePortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().BaseType; +#else + return type.BaseType; +#endif + } + +#if LIBLOG_PORTABLE + internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) + { + return propertyInfo.GetMethod; + } + + internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) + { + return propertyInfo.SetMethod; + } +#endif + +#if !LIBLOG_PORTABLE + internal static object CreateDelegate(this MethodInfo methodInfo, Type delegateType) + { + return Delegate.CreateDelegate(delegateType, methodInfo); + } +#endif + + internal static Assembly GetAssemblyPortable(this Type type) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().Assembly; +#else + return type.Assembly; +#endif + } + } + + internal class DisposableAction : IDisposable + { + private readonly Action _onDispose; + + public DisposableAction(Action onDispose = null) + { + _onDispose = onDispose; + } + + public void Dispose() + { + if(_onDispose != null) + { + _onDispose(); + } + } + } +} diff --git a/src/ScriptCs.Contracts/LogExtensions.cs b/src/ScriptCs.Contracts/LogExtensions.cs new file mode 100644 index 00000000..59f170c9 --- /dev/null +++ b/src/ScriptCs.Contracts/LogExtensions.cs @@ -0,0 +1,251 @@ +namespace ScriptCs.Contracts +{ + using System; + + public static class LogExtensions + { + public static bool IsDebugEnabled(this ILog logger) + { + Guard.AgainstNullArgument("logger", logger); + return logger.Log(LogLevel.Debug); + } + + public static bool IsErrorEnabled(this ILog logger) + { + Guard.AgainstNullArgument("logger", logger); + return logger.Log(LogLevel.Error); + } + + public static bool IsFatalEnabled(this ILog logger) + { + Guard.AgainstNullArgument("logger", logger); + return logger.Log(LogLevel.Fatal); + } + + public static bool IsInfoEnabled(this ILog logger) + { + Guard.AgainstNullArgument("logger", logger); + return logger.Log(LogLevel.Info); + } + + public static bool IsTraceEnabled(this ILog logger) + { + Guard.AgainstNullArgument("logger", logger); + return logger.Log(LogLevel.Trace); + } + + public static bool IsWarnEnabled(this ILog logger) + { + Guard.AgainstNullArgument("logger", logger); + return logger.Log(LogLevel.Warn); + } + + public static void Debug(this ILog logger, Func messageFunc) + { + Guard.AgainstNullArgument("logger", logger); + logger.Log(LogLevel.Debug, messageFunc); + } + + public static void Debug(this ILog logger, string message) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc()); + } + } + + public static void DebugFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsDebugEnabled()) + { + logger.LogFormat(LogLevel.Debug, message, args); + } + } + + public static void DebugException(this ILog logger, string message, Exception exception) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc(), exception); + } + } + + public static void DebugException( + this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsDebugEnabled()) + { + logger.Log(LogLevel.Debug, message.AsFunc(), exception, formatParams); + } + } + + public static void Error(this ILog logger, Func messageFunc) + { + logger.Log(LogLevel.Error, messageFunc); + } + + public static void Error(this ILog logger, string message) + { + if (logger.IsErrorEnabled()) + { + logger.Log(LogLevel.Error, message.AsFunc()); + } + } + + public static void ErrorFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsErrorEnabled()) + { + logger.LogFormat(LogLevel.Error, message, args); + } + } + + public static void ErrorException( + this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsErrorEnabled()) + { + logger.Log(LogLevel.Error, message.AsFunc(), exception, formatParams); + } + } + + public static void Fatal(this ILog logger, Func messageFunc) + { + logger.Log(LogLevel.Fatal, messageFunc); + } + + public static void Fatal(this ILog logger, string message) + { + if (logger.IsFatalEnabled()) + { + logger.Log(LogLevel.Fatal, message.AsFunc()); + } + } + + public static void FatalFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsFatalEnabled()) + { + logger.LogFormat(LogLevel.Fatal, message, args); + } + } + + public static void FatalException( + this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsFatalEnabled()) + { + logger.Log(LogLevel.Fatal, message.AsFunc(), exception, formatParams); + } + } + + public static void Info(this ILog logger, Func messageFunc) + { + Guard.AgainstNullArgument("logger", logger); + logger.Log(LogLevel.Info, messageFunc); + } + + public static void Info(this ILog logger, string message) + { + if (logger.IsInfoEnabled()) + { + logger.Log(LogLevel.Info, message.AsFunc()); + } + } + + public static void InfoFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsInfoEnabled()) + { + logger.LogFormat(LogLevel.Info, message, args); + } + } + + public static void InfoException( + this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsInfoEnabled()) + { + logger.Log(LogLevel.Info, message.AsFunc(), exception, formatParams); + } + } + + public static void Trace(this ILog logger, Func messageFunc) + { + Guard.AgainstNullArgument("logger", logger); + logger.Log(LogLevel.Trace, messageFunc); + } + + public static void Trace(this ILog logger, string message) + { + if (logger.IsTraceEnabled()) + { + logger.Log(LogLevel.Trace, message.AsFunc()); + } + } + + public static void TraceFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsTraceEnabled()) + { + logger.LogFormat(LogLevel.Trace, message, args); + } + } + + public static void TraceException( + this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsTraceEnabled()) + { + logger.Log(LogLevel.Trace, message.AsFunc(), exception, formatParams); + } + } + + public static void Warn(this ILog logger, Func messageFunc) + { + Guard.AgainstNullArgument("logger", logger); + logger.Log(LogLevel.Warn, messageFunc); + } + + public static void Warn(this ILog logger, string message) + { + if (logger.IsWarnEnabled()) + { + logger.Log(LogLevel.Warn, message.AsFunc()); + } + } + + public static void WarnFormat(this ILog logger, string message, params object[] args) + { + if (logger.IsWarnEnabled()) + { + logger.LogFormat(LogLevel.Warn, message, args); + } + } + + public static void WarnException( + this ILog logger, string message, Exception exception, params object[] formatParams) + { + if (logger.IsWarnEnabled()) + { + logger.Log(LogLevel.Warn, message.AsFunc(), exception, formatParams); + } + } + + private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args) + { + logger.Log(logLevel, message.AsFunc(), null, args); + } + + // Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b + private static Func AsFunc(this T value) where T : class + { + return value.Return; + } + + private static T Return(this T value) + { + return value; + } + } +} diff --git a/src/ScriptCs.Contracts/LogLevel.cs b/src/ScriptCs.Contracts/LogLevel.cs index bd15dd0e..0a59e0c5 100644 --- a/src/ScriptCs.Contracts/LogLevel.cs +++ b/src/ScriptCs.Contracts/LogLevel.cs @@ -2,9 +2,11 @@ { public enum LogLevel { - Error, - Info, + Trace, Debug, - Trace + Info, + Warn, + Error, + Fatal } } diff --git a/src/ScriptCs.Contracts/LogProviderExtensions.cs b/src/ScriptCs.Contracts/LogProviderExtensions.cs new file mode 100644 index 00000000..8bca68bd --- /dev/null +++ b/src/ScriptCs.Contracts/LogProviderExtensions.cs @@ -0,0 +1,67 @@ +namespace ScriptCs.Contracts +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + + public static class LogProviderExtensions + { + public static ILog For(this ILogProvider provider) + { + return provider.For(typeof(T)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static ILog ForCurrentType(this ILogProvider provider) + { + return provider.For(new StackFrame(1, false).GetMethod().DeclaringType); + } + + public static ILog For(this ILogProvider provider, Type type) + { + return provider.For(type.FullName); + } + + public static ILog For(this ILogProvider provider, string name) + { + return new LoggerExecutionWrapper(provider.GetLogger(name)); + } + + private class LoggerExecutionWrapper : ILog + { + private const string FailedToGenerateLogMessage = "Failed to generate log message"; + + private readonly Logger _logger; + + internal LoggerExecutionWrapper(Logger logger) + { + _logger = logger; + } + + public bool Log( + LogLevel logLevel, Func createMessage, Exception exception = null, params object[] formatArgs) + { + if (createMessage == null) + { + return _logger(logLevel, null); + } + + Func wrappedMessageFunc = () => + { + try + { + return createMessage(); + } + catch (Exception ex) + { + Log(LogLevel.Error, () => FailedToGenerateLogMessage, ex); + } + + return null; + }; + + return _logger(logLevel, wrappedMessageFunc, exception, formatArgs); + } + } + } +} diff --git a/src/ScriptCs.Contracts/Logger.cs b/src/ScriptCs.Contracts/Logger.cs new file mode 100644 index 00000000..71e8b661 --- /dev/null +++ b/src/ScriptCs.Contracts/Logger.cs @@ -0,0 +1,7 @@ +namespace ScriptCs.Contracts +{ + using System; + + public delegate bool Logger( + LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters); +} diff --git a/src/ScriptCs.Contracts/ModuleAttribute.cs b/src/ScriptCs.Contracts/ModuleAttribute.cs index 1246b129..ddc4fc3a 100644 --- a/src/ScriptCs.Contracts/ModuleAttribute.cs +++ b/src/ScriptCs.Contracts/ModuleAttribute.cs @@ -10,10 +10,13 @@ public class ModuleAttribute : ExportAttribute, IModuleMetadata public ModuleAttribute(string name) : base(typeof(IModule)) { Name = name; + Autoload = false; } public string Name { get; private set; } public string Extensions { get; set; } + + public bool Autoload { get; set; } } } \ No newline at end of file diff --git a/src/ScriptCs.Contracts/Printers.cs b/src/ScriptCs.Contracts/Printers.cs new file mode 100644 index 00000000..73a8e9ab --- /dev/null +++ b/src/ScriptCs.Contracts/Printers.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace ScriptCs.Contracts +{ + public class Printers + { + private readonly IObjectSerializer _serializer; + private readonly Dictionary> _dictionary = new Dictionary>(); + public Printers(IObjectSerializer serializer) + { + _serializer = serializer; + } + + public void AddCustomPrinter(Func printer) + { + _dictionary[typeof(T)] = x => printer((T)x); + } + + private string GetStringFor(Type t, object obj) + { + Func printer; + if (_dictionary.TryGetValue(t, out printer)) + { + return printer(obj); + } + return _serializer.Serialize(obj); + } + + public string GetStringFor(object obj) + { + return GetStringFor(obj.GetType(), obj); + } + + public string GetStringFor(T obj) + { + return GetStringFor(typeof(T), obj); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Contracts/ProjectItem.cs b/src/ScriptCs.Contracts/ProjectItem.cs new file mode 100644 index 00000000..5a11d254 --- /dev/null +++ b/src/ScriptCs.Contracts/ProjectItem.cs @@ -0,0 +1,16 @@ +using System; + +namespace ScriptCs.Contracts +{ + public class ProjectItem + { + public ProjectItem(Guid project, Guid parent) + { + Project = project; + Parent = parent; + } + + public Guid Parent { get; private set; } + public Guid Project { get; private set; } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Contracts/Properties/AssemblyInfo.cs b/src/ScriptCs.Contracts/Properties/AssemblyInfo.cs deleted file mode 100644 index cb1fda07..00000000 --- a/src/ScriptCs.Contracts/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("ScriptCs.Contracts")] -[assembly: AssemblyDescription("This assembly contains the components necessary to create script packs for scriptcs.")] - -[assembly: Guid("a0cbc203-deff-4b7c-b414-11797b7a3100")] diff --git a/src/ScriptCs.Contracts/Properties/ScriptCs.Contracts.nuspec b/src/ScriptCs.Contracts/Properties/ScriptCs.Contracts.nuspec index fe55e48c..be678b1b 100644 --- a/src/ScriptCs.Contracts/Properties/ScriptCs.Contracts.nuspec +++ b/src/ScriptCs.Contracts/Properties/ScriptCs.Contracts.nuspec @@ -3,7 +3,7 @@ ScriptCs.Contracts $version$ - Glenn Block, Justin Rusbatch, Filip Wojcieszyn + Glenn Block, Filip Wojcieszyn, Justin Rusbatch, Kristian Hellang, Damian Schenkelman, Adam Ralph Glenn Block, Justin Rusbatch, Filip Wojcieszyn https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md http://scriptcs.net diff --git a/src/ScriptCs.Contracts/ScriptCs.Contracts.csproj b/src/ScriptCs.Contracts/ScriptCs.Contracts.csproj index c06aeae7..8d7c12e5 100644 --- a/src/ScriptCs.Contracts/ScriptCs.Contracts.csproj +++ b/src/ScriptCs.Contracts/ScriptCs.Contracts.csproj @@ -1,76 +1,26 @@ - - - + - {6049E205-8B5F-4080-B023-70600E51FD64} - Library - ScriptCs.Contracts - ScriptCs.Contracts - ..\..\ + 1.0.0 + netstandard2.0 + ScriptCs.Contracts + Glenn Block, Filip Wojcieszyn, Justin Rusbatch + https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md + http://scriptcs.net + http://www.gravatar.com/avatar/5c754f646971d8bc800b9d4057931938.png?s=120 + ScriptCs.Contracts + ScriptCs.Contracts contains the components necessary to create script packs for scriptcs. + roslyn csx script packs scriptcs + LIBLOG_PROVIDERS_ONLY - - - - - - - - + - - - - Guard.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - - - - - - + + - + + - - \ No newline at end of file diff --git a/src/ScriptCs.Contracts/ScriptPackSession.cs b/src/ScriptCs.Contracts/ScriptPackSession.cs index e83195a5..80851347 100644 --- a/src/ScriptCs.Contracts/ScriptPackSession.cs +++ b/src/ScriptCs.Contracts/ScriptPackSession.cs @@ -37,20 +37,11 @@ private void AddScriptContextNamespace() } } - public IEnumerable Contexts - { - get { return _contexts; } - } + public virtual IEnumerable Contexts => _contexts; - public IEnumerable References - { - get { return _references; } - } + public IEnumerable References => _references; - public IEnumerable Namespaces - { - get { return _namespaces; } - } + public IEnumerable Namespaces => _namespaces; public void InitializePacks() { @@ -68,15 +59,9 @@ public void TerminatePacks() } } - public IDictionary State - { - get { return _state; } - } + public IDictionary State => _state; - public string[] ScriptArgs - { - get { return _scriptArgs; } - } + public string[] ScriptArgs => _scriptArgs; void IScriptPackSession.AddReference(string assemblyDisplayNameOrPath) { @@ -87,5 +72,6 @@ void IScriptPackSession.ImportNamespace(string @namespace) { _namespaces.Add(@namespace); } + } } diff --git a/src/ScriptCs.Contracts/ScriptResult.cs b/src/ScriptCs.Contracts/ScriptResult.cs index 6c046300..f02001af 100644 --- a/src/ScriptCs.Contracts/ScriptResult.cs +++ b/src/ScriptCs.Contracts/ScriptResult.cs @@ -1,42 +1,64 @@ using System; +using System.Linq; +using System.Collections.Generic; using System.Runtime.ExceptionServices; namespace ScriptCs.Contracts { public class ScriptResult { - public object ReturnValue { get; set; } + private readonly HashSet _invalidNamespaces = new HashSet(); - public ExceptionDispatchInfo ExecuteExceptionInfo { get; set; } + public static readonly ScriptResult Empty = new ScriptResult(); - public ExceptionDispatchInfo CompileExceptionInfo { get; set; } + public static readonly ScriptResult Incomplete = new ScriptResult { IsCompleteSubmission = false }; - public bool IsPendingClosingChar { get; set; } - - public char? ExpectingClosingChar { get; set; } - - public void UpdateClosingExpectation(Exception ex) + public ScriptResult() { - Guard.AgainstNullArgument("ex", ex); + // Explicit default ctor to use as mock return value. + IsCompleteSubmission = true; + } - var message = ex.Message; - char? closingChar = null; + public ScriptResult( + object returnValue = null, + Exception executionException = null, + Exception compilationException = null, + IEnumerable invalidNamespaces = null) + { + if (returnValue != null) + { + ReturnValue = returnValue; + } - if (message.Contains("CS1026: ) expected")) + if (executionException != null) { - closingChar = ')'; + ExecuteExceptionInfo = ExceptionDispatchInfo.Capture(executionException); } - else if (message.Contains("CS1513: } expected")) + + if (compilationException != null) { - closingChar = '}'; + CompileExceptionInfo = ExceptionDispatchInfo.Capture(compilationException); } - else if (message.Contains("CS1003: Syntax error, ']' expected")) + + if (invalidNamespaces != null) { - closingChar = ']'; + foreach (var ns in invalidNamespaces.Distinct()) + { + _invalidNamespaces.Add(ns); + } } - ExpectingClosingChar = closingChar; - IsPendingClosingChar = closingChar.HasValue; + IsCompleteSubmission = true; } + + public object ReturnValue { get; private set; } + + public ExceptionDispatchInfo ExecuteExceptionInfo { get; private set; } + + public ExceptionDispatchInfo CompileExceptionInfo { get; private set; } + + public IEnumerable InvalidNamespaces => _invalidNamespaces.ToArray(); + + public bool IsCompleteSubmission { get; private set; } } } \ No newline at end of file diff --git a/src/ScriptCs.Core/AppDomainAssemblyResolver.cs b/src/ScriptCs.Core/AppDomainAssemblyResolver.cs new file mode 100644 index 00000000..925420fc --- /dev/null +++ b/src/ScriptCs.Core/AppDomainAssemblyResolver.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using ScriptCs.Contracts; +using System.IO; + +namespace ScriptCs +{ + public class AppDomainAssemblyResolver : IAppDomainAssemblyResolver + { + private readonly ILog _logger; + private readonly IFileSystem _fileSystem; + private readonly IAssemblyResolver _resolver; + private readonly IAssemblyUtility _assemblyUtility; + private readonly IDictionary _assemblyInfoMap; + + public AppDomainAssemblyResolver( + ILogProvider logProvider, + IFileSystem fileSystem, + IAssemblyResolver resolver, + IAssemblyUtility assemblyUtility, + IDictionary assemblyInfoMap = null, + Func resolveHandler = null) + { + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("resolver", resolver); + Guard.AgainstNullArgument("assemblyUtility", assemblyUtility); + + _assemblyInfoMap = assemblyInfoMap ?? new Dictionary(); + _assemblyUtility = assemblyUtility; + _logger = logProvider.ForCurrentType(); + _fileSystem = fileSystem; + _resolver = resolver; + + if (resolveHandler == null) + { + resolveHandler = AssemblyResolve; + } + + AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(resolveHandler); + } + + internal Assembly AssemblyResolve(object sender, ResolveEventArgs args) + { + AssemblyInfo assemblyInfo; + var name = new AssemblyName(args.Name); + + if (_assemblyInfoMap.TryGetValue(name.Name, out assemblyInfo)) + { + lock (assemblyInfo) + { + if (assemblyInfo.Assembly == null) + { + assemblyInfo.Assembly = _assemblyUtility.LoadFile(assemblyInfo.Path); + } + } + + _logger.DebugFormat("Resolving from: {0} to: {1}", args.Name, assemblyInfo.Assembly.GetName()); + return assemblyInfo.Assembly; + } + + return null; + } + + public void Initialize() + { + var hostAssemblyPaths = _fileSystem.EnumerateBinaries(_fileSystem.HostBin, SearchOption.TopDirectoryOnly); + AddAssemblyPaths(hostAssemblyPaths); + + var globalPaths = _resolver.GetAssemblyPaths(_fileSystem.GlobalFolder, true); + AddAssemblyPaths(globalPaths); + + var scriptAssemblyPaths = _resolver.GetAssemblyPaths(_fileSystem.CurrentDirectory, true); + AddAssemblyPaths(scriptAssemblyPaths); + } + + public virtual void AddAssemblyPaths(IEnumerable assemblyPaths) + { + Guard.AgainstNullArgument("assemblyPaths", assemblyPaths); + + foreach (var assemblyPath in assemblyPaths) + { + if (_assemblyUtility.IsManagedAssembly(assemblyPath)) + { + var info = new AssemblyInfo { Path = assemblyPath }; + var name = _assemblyUtility.GetAssemblyName(assemblyPath); + info.Version = name.Version; + + AssemblyInfo foundInfo; + var found = _assemblyInfoMap.TryGetValue(name.Name, out foundInfo); + + if (!found || foundInfo.Version.CompareTo(info.Version) < 0) + { + // if the assembly being passed is a higher version + // and an assembly with it's name has already been resolved + if (found && foundInfo.Assembly != null) + { + _logger.WarnFormat( + "Conflict: Assembly {0} with version {1} cannot be added as it has already been resolved", + assemblyPath, + info.Version); + + continue; + } + + _logger.DebugFormat("Mapping Assembly {0} to version:{1}", name.Name, name.Version); + _assemblyInfoMap[name.Name] = info; + } + } + else + { + _logger.DebugFormat("Skipping Mapping Native Assembly {0}", assemblyPath); + } + } + } + } +} diff --git a/src/ScriptCs.Core/AssemblyInfo.cs b/src/ScriptCs.Core/AssemblyInfo.cs new file mode 100644 index 00000000..fb45aac9 --- /dev/null +++ b/src/ScriptCs.Core/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System; +using System.Reflection; + +namespace ScriptCs +{ + public class AssemblyInfo + { + public string Path { get; set; } + public Assembly Assembly { get; set; } + public Version Version { get; set; } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/AssemblyResolver.cs b/src/ScriptCs.Core/AssemblyResolver.cs index 9975ce82..d3a0f058 100644 --- a/src/ScriptCs.Core/AssemblyResolver.cs +++ b/src/ScriptCs.Core/AssemblyResolver.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; - -using Common.Logging; - using ScriptCs.Contracts; namespace ScriptCs @@ -11,83 +9,72 @@ namespace ScriptCs public class AssemblyResolver : IAssemblyResolver { private readonly Dictionary> _assemblyPathCache = new Dictionary>(); - private readonly IFileSystem _fileSystem; - private readonly IPackageAssemblyResolver _packageAssemblyResolver; - - private readonly ILog _logger; - private readonly IAssemblyUtility _assemblyUtility; + private readonly ILog _logger; public AssemblyResolver( IFileSystem fileSystem, IPackageAssemblyResolver packageAssemblyResolver, IAssemblyUtility assemblyUtility, - ILog logger) + ILogProvider logProvider) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgumentProperty("fileSystem", "PackagesFolder", fileSystem.PackagesFolder); + Guard.AgainstNullArgumentProperty("fileSystem", "BinFolder", fileSystem.BinFolder); + + Guard.AgainstNullArgument("packageAssemblyResolver", packageAssemblyResolver); + Guard.AgainstNullArgument("assemblyUtility", assemblyUtility); + Guard.AgainstNullArgument("logProvider", logProvider); + _fileSystem = fileSystem; _packageAssemblyResolver = packageAssemblyResolver; - _logger = logger; _assemblyUtility = assemblyUtility; + _logger = logProvider.ForCurrentType(); } - public IEnumerable GetAssemblyPaths(string path) + public IEnumerable GetAssemblyPaths(string path, bool binariesOnly = false) { Guard.AgainstNullArgument("path", path); List assemblies; - if (_assemblyPathCache.TryGetValue(path, out assemblies)) + if (!_assemblyPathCache.TryGetValue(path, out assemblies)) { - return assemblies; + assemblies = GetPackageAssemblyNames(path).Union(GetBinAssemblyPaths(path)).ToList(); + _assemblyPathCache.Add(path, assemblies); } - - var packageAssemblies = GetPackageAssemblies(path); - var binAssemblies = GetBinAssemblies(path); - - assemblies = packageAssemblies.Union(binAssemblies).ToList(); - _assemblyPathCache.Add(path, assemblies); - - return assemblies; + + return binariesOnly + ? assemblies.Where(m => + m.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) || + m.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)) + : assemblies.ToArray(); } - private IEnumerable GetBinAssemblies(string path) + private IEnumerable GetBinAssemblyPaths(string path) { - var binFolder = Path.Combine(path, Constants.BinFolder); + var binFolder = Path.Combine(path, _fileSystem.BinFolder); if (!_fileSystem.DirectoryExists(binFolder)) { - return Enumerable.Empty(); + yield break; } - var assemblies = _fileSystem.EnumerateFiles(binFolder, "*.dll") - .Union(_fileSystem.EnumerateFiles(binFolder, "*.exe")) - .Where(f => _assemblyUtility.IsManagedAssembly(f)) - .ToList(); - - foreach (var assembly in assemblies) + foreach (var assembly in _fileSystem.EnumerateBinaries(binFolder, SearchOption.TopDirectoryOnly) + .Where(f => _assemblyUtility.IsManagedAssembly(f))) { _logger.DebugFormat("Found assembly in bin folder: {0}", Path.GetFileName(assembly)); + yield return assembly; } - - return assemblies; } - private IEnumerable GetPackageAssemblies(string path) + private IEnumerable GetPackageAssemblyNames(string path) { - var packagesFolder = Path.Combine(path, Constants.PackagesFolder); - if (!_fileSystem.DirectoryExists(packagesFolder)) + foreach (var assembly in _packageAssemblyResolver.GetAssemblyNames(path)) { - return Enumerable.Empty(); + _logger.DebugFormat("Found package assembly: {0}", Path.GetFileName(assembly)); + yield return assembly; } - - var assemblies = _packageAssemblyResolver.GetAssemblyNames(path).ToList(); - - foreach (var packageAssembly in assemblies) - { - _logger.DebugFormat("Found package assembly: {0}", Path.GetFileName(packageAssembly)); - } - - return assemblies; } } } diff --git a/src/ScriptCs.Core/AssemblyUtility.cs b/src/ScriptCs.Core/AssemblyUtility.cs index 1f4de0d7..80e37c0f 100644 --- a/src/ScriptCs.Core/AssemblyUtility.cs +++ b/src/ScriptCs.Core/AssemblyUtility.cs @@ -1,11 +1,10 @@ using System; using System.Reflection; - using ScriptCs.Contracts; namespace ScriptCs { - public class AssemblyUtility : IAssemblyUtility + public class AssemblyUtility : IAssemblyUtility { public bool IsManagedAssembly(string path) { @@ -19,5 +18,11 @@ public bool IsManagedAssembly(string path) return false; } } + + public Assembly LoadFile(string path) => Assembly.LoadFile(path); + + public Assembly Load(AssemblyName assemblyRef) => Assembly.Load(assemblyRef); + + public AssemblyName GetAssemblyName(string path) => AssemblyName.GetAssemblyName(path); } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Core/Constants.cs b/src/ScriptCs.Core/Constants.cs index 116c7f0e..99658500 100644 --- a/src/ScriptCs.Core/Constants.cs +++ b/src/ScriptCs.Core/Constants.cs @@ -1,14 +1,26 @@ +using System; + namespace ScriptCs { public static class Constants { + [Obsolete("Use IFileSystem instead.")] public const string PackagesFile = "packages.config"; + + [Obsolete("Use IFileSystem instead.")] public const string NugetFile = "nuget.config"; + + [Obsolete("Use IFileSystem instead.")] public const string PackagesFolder = "packages"; + + [Obsolete("Use IFileSystem instead.")] public const string BinFolder = "bin"; + + [Obsolete("Use IFileSystem instead.")] public const string DllCacheFolder = ".cache"; + + public const string ConfigFilename = "scriptcs.opts"; + public const string DefaultRepositoryUrl = "https://nuget.org/api/v2/"; - public const string DebugContractName = "Debug"; - public const string RunContractName = "Run"; } } diff --git a/src/ScriptCs.Core/DebugScriptExecutor.cs b/src/ScriptCs.Core/DebugScriptExecutor.cs index 7c3ce2c6..b9764f8a 100644 --- a/src/ScriptCs.Core/DebugScriptExecutor.cs +++ b/src/ScriptCs.Core/DebugScriptExecutor.cs @@ -1,13 +1,12 @@ -using Common.Logging; - +using System; using ScriptCs.Contracts; namespace ScriptCs { public class DebugScriptExecutor : ScriptExecutor { - public DebugScriptExecutor(IFileSystem fileSystem, IFilePreProcessor filePreProcessor, IScriptEngine scriptEngine, ILog logger) - : base(fileSystem, filePreProcessor, scriptEngine, logger) + public DebugScriptExecutor(IFileSystem fileSystem, IFilePreProcessor filePreProcessor, IScriptEngine scriptEngine, ILogProvider logProvider, IScriptLibraryComposer composer, IScriptInfo scriptInfo) + : base(fileSystem, filePreProcessor, scriptEngine, logProvider, composer, scriptInfo) { } } diff --git a/src/ScriptCs.Core/Exceptions/MissingAssemblyException.cs b/src/ScriptCs.Core/Exceptions/MissingAssemblyException.cs deleted file mode 100644 index 5b2e2253..00000000 --- a/src/ScriptCs.Core/Exceptions/MissingAssemblyException.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace ScriptCs.Exceptions -{ - [Serializable] - public class MissingAssemblyException : Exception - { - public MissingAssemblyException(string message) - : base(message) - { - } - - public MissingAssemblyException(string message, Exception innerException) - : base(message, innerException) - { - } - - protected MissingAssemblyException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/src/ScriptCs.Core/Exceptions/ScriptPackException.cs b/src/ScriptCs.Core/Exceptions/ScriptPackException.cs new file mode 100644 index 00000000..7c98d5e9 --- /dev/null +++ b/src/ScriptCs.Core/Exceptions/ScriptPackException.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.Serialization; + +namespace ScriptCs.Exceptions +{ + [Serializable] + public class ScriptPackException : Exception + { + public ScriptPackException(string message) + : base(message) + { + } + + protected ScriptPackException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/ScriptCs.Core/Extensions/EnumerableExtensions.cs b/src/ScriptCs.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..b7aa9ce7 --- /dev/null +++ b/src/ScriptCs.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ScriptCs.Extensions +{ + internal static class EnumerableExtensions + { + internal static bool IsNullOrEmpty(this IEnumerable enumerable) + { + if (enumerable == null) + { + return true; + } + + var collection = enumerable as ICollection; + if (collection != null) + { + return collection.Count == 0; + } + + return !enumerable.Any(); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/Extensions/MethodInfoExtensions.cs b/src/ScriptCs.Core/Extensions/MethodInfoExtensions.cs new file mode 100644 index 00000000..5d083c63 --- /dev/null +++ b/src/ScriptCs.Core/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace ScriptCs.Extensions +{ + internal static class MethodInfoExtensions + { + internal static IEnumerable GetParametersWithoutExtensions(this MethodInfo method) + { + IEnumerable methodParams = method.GetParameters(); + if (method.IsDefined(typeof(ExtensionAttribute), false)) + { + methodParams = methodParams.Skip(1); + } + + return methodParams; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/Extensions/TypeExtensions.cs b/src/ScriptCs.Core/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..4d51b6a7 --- /dev/null +++ b/src/ScriptCs.Core/Extensions/TypeExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace ScriptCs.Extensions +{ + internal static class TypeExtensions + { + internal static IEnumerable GetExtensionMethods(this Type type) + { + return type.Assembly.GetExportedTypes().Where(x => !x.IsGenericType && !x.IsNested && x.IsSealed) + .SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.Public)) + .Where(x => x.IsDefined(typeof(ExtensionAttribute), false)) + .Where(x => x.GetParameters()[0].ParameterType == type); + } + + internal static IEnumerable GetAllMethods(this Type type) + { + return type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) + .Where(m => !m.IsSpecialName).Union(type.GetExtensionMethods()).OrderBy(x => x.Name); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/FilePreProcessor.cs b/src/ScriptCs.Core/FilePreProcessor.cs index 0fd2e180..044d4ec0 100644 --- a/src/ScriptCs.Core/FilePreProcessor.cs +++ b/src/ScriptCs.Core/FilePreProcessor.cs @@ -2,10 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; - -using Common.Logging; - using ScriptCs.Contracts; namespace ScriptCs @@ -15,14 +11,19 @@ public class FilePreProcessor : IFilePreProcessor private readonly ILog _logger; private readonly IEnumerable _lineProcessors; + private readonly IEnumerable _directiveLineProcessors; private readonly IFileSystem _fileSystem; - public FilePreProcessor(IFileSystem fileSystem, ILog logger, IEnumerable lineProcessors) + public FilePreProcessor(IFileSystem fileSystem, ILogProvider logProvider, IEnumerable lineProcessors) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("logProvider", logProvider); + _fileSystem = fileSystem; - _logger = logger; + _logger = logProvider.ForCurrentType(); _lineProcessors = lineProcessors; + _directiveLineProcessors = _lineProcessors.OfType(); } public virtual FilePreProcessorResult ProcessFile(string path) @@ -55,30 +56,15 @@ protected virtual FilePreProcessorResult Process(Action parse Namespaces = context.Namespaces, LoadedScripts = context.LoadedScripts, References = context.References, - Code = code + Code = code, + ScriptPath = context.ScriptPath }; } protected virtual string GenerateCode(FileParserContext context) { Guard.AgainstNullArgument("context", context); - - var stringBuilder = new StringBuilder(); - - var usingLines = context.Namespaces - .Where(ns => !string.IsNullOrWhiteSpace(ns)) - .Select(ns => string.Format("using {0};", ns)) - .ToList(); - - if (usingLines.Count > 0) - { - stringBuilder.AppendLine(string.Join(_fileSystem.NewLine, usingLines)); - stringBuilder.AppendLine(); // Insert a blank separator line - } - - stringBuilder.Append(string.Join(_fileSystem.NewLine, context.BodyLines)); - - return stringBuilder.ToString(); + return string.Join(_fileSystem.NewLine, context.BodyLines); } public virtual void ParseFile(string path, FileParserContext context) @@ -97,11 +83,18 @@ public virtual void ParseFile(string path, FileParserContext context) _logger.DebugFormat("Processing {0}...", filename); - // Add script to loaded collection before parsing to avoid loop. - context.LoadedScripts.Add(fullPath); + if (context.ScriptPath == null) + { + context.ScriptPath = fullPath; + } + else + { + // Add script to loaded collection before parsing to avoid loop. + context.LoadedScripts.Add(fullPath); + } var scriptLines = _fileSystem.ReadFileLines(fullPath).ToList(); - + InsertLineDirective(fullPath, scriptLines); InDirectory(fullPath, () => ParseScript(scriptLines, context)); } @@ -119,7 +112,6 @@ public virtual void ParseScript(List scriptLines, FileParserContext cont var isBeforeCode = index < codeIndex || codeIndex < 0; var wasProcessed = _lineProcessors.Any(x => x.ProcessLine(this, context, line, isBeforeCode)); - if (!wasProcessed) { context.BodyLines.Add(line); @@ -151,10 +143,13 @@ private void InDirectory(string path, Action action) _fileSystem.CurrentDirectory = oldCurrentDirectory; } - private static bool IsNonDirectiveLine(string line) + private bool IsNonDirectiveLine(string line) { - var trimmedLine = line.TrimStart(' '); - return !trimmedLine.StartsWith("#r ") && !trimmedLine.StartsWith("#load ") && line.Trim() != string.Empty; + line = line.Trim(); + if (line.StartsWith("//") || line.Equals(string.Empty)) + return false; + + return !_directiveLineProcessors.Any(lp => lp.Matches(line)); } private static bool IsUsingLine(string line) diff --git a/src/ScriptCs.Core/FileSystem.cs b/src/ScriptCs.Core/FileSystem.cs index 7b243bd2..6bd0ef84 100644 --- a/src/ScriptCs.Core/FileSystem.cs +++ b/src/ScriptCs.Core/FileSystem.cs @@ -1,29 +1,63 @@ using System; using System.Collections.Generic; using System.IO; - using ScriptCs.Contracts; namespace ScriptCs { public class FileSystem : IFileSystem { - public IEnumerable EnumerateFiles(string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) + public virtual IEnumerable EnumerateFiles( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) { return Directory.EnumerateFiles(dir, searchPattern, searchOption); } - public void Copy(string source, string dest, bool overwrite) + public virtual IEnumerable EnumerateDirectories( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) + { + return Directory.EnumerateDirectories(dir, searchPattern, searchOption); + } + + public virtual IEnumerable EnumerateFilesAndDirectories( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) + { + return Directory.EnumerateFileSystemEntries(dir, searchPattern, searchOption); + } + + public virtual void Copy(string source, string dest, bool overwrite) { File.Copy(source, dest, overwrite); } - public bool DirectoryExists(string path) + public virtual void CopyDirectory(string source, string dest, bool overwrite) + { + // NOTE: adding guards since the exceptions thrown by System.IO would be confusing + Guard.AgainstNullArgument("source", source); + Guard.AgainstNullArgument("dest", dest); + + if (!Directory.Exists(dest)) + { + Directory.CreateDirectory(dest); + } + + foreach (var file in Directory.GetFiles(source)) + { + File.Copy(file, Path.Combine(dest, Path.GetFileName(file)), overwrite); + } + + foreach (var directory in Directory.GetDirectories(source)) + { + CopyDirectory(directory, Path.Combine(dest, Path.GetFileName(directory)), overwrite); + } + } + + public virtual bool DirectoryExists(string path) { return Directory.Exists(path); } - public void CreateDirectory(string path, bool hidden) + public virtual void CreateDirectory(string path, bool hidden) { var directory = Directory.CreateDirectory(path); @@ -33,88 +67,85 @@ public void CreateDirectory(string path, bool hidden) } } - public void DeleteDirectory(string path) + public virtual void DeleteDirectory(string path) { Directory.Delete(path, true); } - public string ReadFile(string path) + public virtual string ReadFile(string path) { return File.ReadAllText(path); } - public string[] ReadFileLines(string path) + public virtual string[] ReadFileLines(string path) { return File.ReadAllLines(path); } - public bool IsPathRooted(string path) + public virtual bool IsPathRooted(string path) { return Path.IsPathRooted(path); } - public string CurrentDirectory + public virtual string CurrentDirectory { - get { return Environment.CurrentDirectory; } - set { Environment.CurrentDirectory = value; } + get => Environment.CurrentDirectory; + set => Environment.CurrentDirectory = value; } - public string NewLine - { - get { return Environment.NewLine; } - } + public virtual string NewLine => Environment.NewLine; - public DateTime GetLastWriteTime(string file) + public virtual DateTime GetLastWriteTime(string file) { return File.GetLastWriteTime(file); } - public void Move(string source, string dest) + public virtual void Move(string source, string dest) { File.Move(source, dest); } - public bool FileExists(string path) + public virtual void MoveDirectory(string source, string dest) + { + Directory.Move(source, dest); + } + + public virtual bool FileExists(string path) { return File.Exists(path); } - public void FileDelete(string path) + public virtual void FileDelete(string path) { File.Delete(path); } - public IEnumerable SplitLines(string value) + public virtual IEnumerable SplitLines(string value) { Guard.AgainstNullArgument("value", value); return value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); } - public void WriteToFile(string path, string text) + public virtual void WriteToFile(string path, string text) { File.WriteAllText(path, text); } - public Stream CreateFileStream(string filePath, FileMode mode) + public virtual Stream CreateFileStream(string filePath, FileMode mode) { return new FileStream(filePath, mode); } - public void WriteAllBytes(string filePath, byte[] bytes) + public virtual void WriteAllBytes(string filePath, byte[] bytes) { File.WriteAllBytes(filePath, bytes); } - public string ModulesFolder - { - get - { - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "scriptcs"); - } - } + public virtual string GlobalFolder => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "scriptcs"); - public string GetWorkingDirectory(string path) + public virtual string GetWorkingDirectory(string path) { if (string.IsNullOrWhiteSpace(path)) { @@ -125,9 +156,7 @@ public string GetWorkingDirectory(string path) if (FileExists(realPath) || DirectoryExists(realPath)) { - var attributes = File.GetAttributes(realPath); - - if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) + if ((File.GetAttributes(realPath) & FileAttributes.Directory) == FileAttributes.Directory) { return realPath; } @@ -138,9 +167,26 @@ public string GetWorkingDirectory(string path) return Path.GetDirectoryName(realPath); } - public string GetFullPath(string path) + public virtual string GetFullPath(string path) { return Path.GetFullPath(path); } + + + public virtual string TempPath => Path.GetTempPath(); + + public virtual string HostBin => AppDomain.CurrentDomain.BaseDirectory; + + public virtual string BinFolder => "scriptcs_bin"; + + public virtual string DllCacheFolder => ".scriptcs_cache"; + + public virtual string PackagesFile => "scriptcs_packages.config"; + + public virtual string PackagesFolder => "scriptcs_packages"; + + public virtual string NugetFile => "scriptcs_nuget.config"; + + public virtual string GlobalOptsFile => Path.Combine(GlobalFolder, Constants.ConfigFilename); } } diff --git a/src/ScriptCs.Core/FrameworkUtils.cs b/src/ScriptCs.Core/FrameworkUtils.cs new file mode 100644 index 00000000..6a1935fc --- /dev/null +++ b/src/ScriptCs.Core/FrameworkUtils.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace ScriptCs +{ + public static class FrameworkUtils + { + private static string _frameworkName; + public static string FrameworkName + { + get + { + if (_frameworkName == null) + { + // in order to handle the weird behavior of old nuget packages + // with NET Standard 2.0, we'll use the entry assembly if possible + var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); + + //Thanks to Dave Glick for this code contribution + var frameworkName = assembly.GetCustomAttributes(true) + .OfType() + .Select(x => x.FrameworkName) + .FirstOrDefault(); + _frameworkName = frameworkName; + } + return _frameworkName; + } + } + } +} diff --git a/src/ScriptCs.Core/ILoggerConfigurator.cs b/src/ScriptCs.Core/ILoggerConfigurator.cs index 65e62537..a10efab0 100644 --- a/src/ScriptCs.Core/ILoggerConfigurator.cs +++ b/src/ScriptCs.Core/ILoggerConfigurator.cs @@ -1,4 +1,4 @@ -using Common.Logging; +using System; using ScriptCs.Contracts; namespace ScriptCs @@ -6,7 +6,5 @@ namespace ScriptCs public interface ILoggerConfigurator { void Configure(IConsole console); - - ILog GetLogger(); } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Core/LoadLineProcessor.cs b/src/ScriptCs.Core/LoadLineProcessor.cs index 0aa86d4f..8313b943 100644 --- a/src/ScriptCs.Core/LoadLineProcessor.cs +++ b/src/ScriptCs.Core/LoadLineProcessor.cs @@ -14,18 +14,14 @@ public class LoadLineProcessor : DirectiveLineProcessor, ILoadLineProcessor public LoadLineProcessor(IFileSystem fileSystem) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + _fileSystem = fileSystem; } - protected override string DirectiveName - { - get { return "load"; } - } + protected override string DirectiveName => "load"; - protected override BehaviorAfterCode BehaviorAfterCode - { - get { return BehaviorAfterCode.Throw; } - } + protected override BehaviorAfterCode BehaviorAfterCode => BehaviorAfterCode.Throw; protected override bool ProcessLine(IFileParser parser, FileParserContext context, string line) { diff --git a/src/ScriptCs.Core/NullScriptLibraryComposer.cs b/src/ScriptCs.Core/NullScriptLibraryComposer.cs new file mode 100644 index 00000000..a4b637d2 --- /dev/null +++ b/src/ScriptCs.Core/NullScriptLibraryComposer.cs @@ -0,0 +1,14 @@ +using ScriptCs.Contracts; +using System.Text; + +namespace ScriptCs +{ + public class NullScriptLibraryComposer : IScriptLibraryComposer + { + public void Compose(string workingDirectory, StringBuilder builder = null) + { + } + + public string ScriptLibrariesFile => string.Empty; + } +} diff --git a/src/ScriptCs.Core/PackageAssemblyResolver.cs b/src/ScriptCs.Core/PackageAssemblyResolver.cs index 177eba3f..860344ce 100644 --- a/src/ScriptCs.Core/PackageAssemblyResolver.cs +++ b/src/ScriptCs.Core/PackageAssemblyResolver.cs @@ -2,10 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Common.Logging; using ScriptCs.Contracts; -using ScriptCs.Exceptions; -using ScriptCs.Package; namespace ScriptCs { @@ -14,51 +11,44 @@ public class PackageAssemblyResolver : IPackageAssemblyResolver private readonly IFileSystem _fileSystem; private readonly IPackageContainer _packageContainer; private readonly ILog _logger; + private readonly IAssemblyUtility _assemblyUtility; + private List _topLevelPackages; - public PackageAssemblyResolver(IFileSystem fileSystem, IPackageContainer packageContainer, ILog logger) + public PackageAssemblyResolver( + IFileSystem fileSystem, IPackageContainer packageContainer, ILogProvider logProvider, IAssemblyUtility assemblyUtility) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgumentProperty("fileSystem", "PackagesFolder", fileSystem.PackagesFolder); + Guard.AgainstNullArgumentProperty("fileSystem", "PackagesFile", fileSystem.PackagesFile); + + Guard.AgainstNullArgument("packageContainer", packageContainer); + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("assemblyUtility", assemblyUtility); + _fileSystem = fileSystem; _packageContainer = packageContainer; - _logger = logger; + _logger = logProvider.ForCurrentType(); + _assemblyUtility = assemblyUtility; } public void SavePackages() { - var packagesFile = Path.Combine(_fileSystem.CurrentDirectory, Constants.PackagesFile); - var packagesFolder = Path.Combine(_fileSystem.CurrentDirectory, Constants.PackagesFolder); - - if (_fileSystem.FileExists(packagesFile)) - { - _logger.Info("Packages.config already exists!"); - return; - } - + var packagesFolder = Path.Combine(_fileSystem.CurrentDirectory, _fileSystem.PackagesFolder); if (!_fileSystem.DirectoryExists(packagesFolder)) { - _logger.Info("Packages directory does not exist!"); + _logger.Warn("Packages directory does not exist!"); return; } - var result = _packageContainer.CreatePackageFile().ToList(); - if (!result.Any()) - { - _logger.Info("No packages found!"); - return; - } - - result.ForEach(i => _logger.Info(string.Format("Added {0}", i))); - _logger.Info("Packages.config successfully created!"); + _packageContainer.CreatePackageFile(); } public IEnumerable GetPackages(string workingDirectory) { - var packageFile = Path.Combine(workingDirectory, Constants.PackagesFile); - var packages = _packageContainer.FindReferences(packageFile).ToList(); - - _topLevelPackages = packages; - - return packages; + var packagesFile = Path.Combine(workingDirectory, _fileSystem.PackagesFile); + _topLevelPackages = _packageContainer.FindReferences(packagesFile).ToList(); + return _topLevelPackages.ToArray(); } public IEnumerable GetAssemblyNames(string workingDirectory) @@ -69,69 +59,68 @@ public IEnumerable GetAssemblyNames(string workingDirectory) return Enumerable.Empty(); } - var packageFile = Path.Combine(workingDirectory, Constants.PackagesFile); - var packageDir = Path.Combine(workingDirectory, Constants.PackagesFolder); - - var foundAssemblyPaths = new List(); - var missingAssemblies = new List(); - - LoadFiles(packageDir, packages, missingAssemblies, foundAssemblyPaths, _fileSystem.FileExists(packageFile)); + var packagesFile = Path.Combine(workingDirectory, _fileSystem.PackagesFile); + var packagesFolder = Path.Combine(workingDirectory, _fileSystem.PackagesFolder); - if (missingAssemblies.Count > 0) - { - var missingAssembliesString = string.Join(",", missingAssemblies.Select(i => i.PackageId + " " + i.FrameworkName.FullName)); - throw new MissingAssemblyException(string.Format("Missing: {0}", missingAssembliesString)); - } - - return foundAssemblyPaths; + var names = new List(); + GetAssemblyNames(packagesFolder, packages, names, _fileSystem.FileExists(packagesFile)); + return names; } - private void LoadFiles(string packageDir, IEnumerable packageReferences, List missingAssemblies, List foundAssemblies, bool strictLoad = true) + private void GetAssemblyNames( + string packageDir, + IEnumerable packageReferences, + ICollection names, + bool strictLoad) { - foreach (var packageRef in packageReferences) + foreach (var packageReference in packageReferences) { - var nugetPackage = _packageContainer.FindPackage(packageDir, packageRef); - if (nugetPackage == null) + var packageObject = _packageContainer.FindPackage(packageDir, packageReference); + if (packageObject == null) { - missingAssemblies.Add(packageRef); - _logger.Info("Cannot find: " + packageRef.PackageId + " " + packageRef.Version); + _logger.WarnFormat( + "Cannot find: {0} {1}", + packageReference.PackageId, + packageReference.Version); continue; } - var compatibleFiles = nugetPackage.GetCompatibleDlls(packageRef.FrameworkName); - if (compatibleFiles == null) + var compatibleDlls = packageObject.GetCompatibleDlls(packageReference.FrameworkName); + if (compatibleDlls == null) { - missingAssemblies.Add(packageRef); - _logger.Info("Cannot find binaries for " + packageRef.FrameworkName + " in: " + packageRef.PackageId + " " + packageRef.Version); + _logger.WarnFormat( + "Cannot find compatible binaries for {0} in: {1} {2}", + packageReference.FrameworkName, + packageReference.PackageId, + packageReference.Version); continue; } - var compatibleFilePaths = compatibleFiles.Select(packageFile => Path.Combine(packageDir, nugetPackage.FullName, packageFile)); - - foreach (var path in compatibleFilePaths) + foreach (var name in compatibleDlls + .Select(packageFile => Path.Combine(packageDir, packageObject.FullName, packageFile)) + .Where(path => _assemblyUtility.IsManagedAssembly(path)) + .Concat(packageObject.FrameworkAssemblies) + .Where(name => !names.Contains(name))) { - if (foundAssemblies.Contains(path)) - { - continue; - } - - foundAssemblies.Add(path); - _logger.Debug("Found: " + path); + names.Add(name); + _logger.Debug("Found: " + name); } - if (nugetPackage.Dependencies == null || !nugetPackage.Dependencies.Any() || !strictLoad) + if (packageObject.Dependencies == null || !packageObject.Dependencies.Any() || !strictLoad) { continue; } - var dependencyReferences = nugetPackage.Dependencies - .Where(i => _topLevelPackages.All(x => x.PackageId != i.Id)) - .Select(i => new PackageReference(i.Id, i.FrameworkName, i.Version)); + var dependencyReferences = packageObject.Dependencies + .Where(dependency => + _topLevelPackages.All(topLevelPackage => topLevelPackage.PackageId != dependency.Id)) + .Select(dependency => + new PackageReference(dependency.Id, dependency.FrameworkName, dependency.Version)); - LoadFiles(packageDir, dependencyReferences, missingAssemblies, foundAssemblies, true); + GetAssemblyNames(packageDir, dependencyReferences, names, true); } } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Core/PackageReference.cs b/src/ScriptCs.Core/PackageReference.cs index 8bee3e86..dbd70ef3 100644 --- a/src/ScriptCs.Core/PackageReference.cs +++ b/src/ScriptCs.Core/PackageReference.cs @@ -1,9 +1,8 @@ using System; using System.Runtime.Versioning; - using ScriptCs.Contracts; -namespace ScriptCs.Package +namespace ScriptCs { public class PackageReference : IPackageReference { @@ -20,6 +19,13 @@ public PackageReference(string packageId, FrameworkName frameworkName, Version v SpecialVersion = specialVersion; } + public PackageReference(string packageId, FrameworkName frameworkName, string stringVersion) + { + FrameworkName = frameworkName; + PackageId = packageId; + SetVersionFromString(stringVersion); + } + public string PackageId { get; private set; } public FrameworkName FrameworkName { get; private set; } @@ -27,5 +33,28 @@ public PackageReference(string packageId, FrameworkName frameworkName, Version v public Version Version { get; set; } public string SpecialVersion { get; set; } + + private void SetVersionFromString(string stringVersion) + { + if (string.IsNullOrWhiteSpace(stringVersion)) + { + Version = new Version(); + } + else + { + if (stringVersion.Contains("-")) + { + var splitVersion = stringVersion.Split(new[] { '-' }, 2); + if (splitVersion.Length == 2) + { + Version = new Version(splitVersion[0]); + SpecialVersion = splitVersion[1]; + return; + } + } + + Version = new Version(stringVersion); + } + } } } \ No newline at end of file diff --git a/src/ScriptCs.Core/Properties/AssemblyInfo.cs b/src/ScriptCs.Core/Properties/AssemblyInfo.cs index 12e75063..8a1cb333 100644 --- a/src/ScriptCs.Core/Properties/AssemblyInfo.cs +++ b/src/ScriptCs.Core/Properties/AssemblyInfo.cs @@ -1,7 +1,7 @@ -using System.Reflection; +using System; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ScriptCs.Core")] -[assembly: AssemblyDescription("ScriptCs.Core is the core framework assembly for scriptcs.")] - -[assembly: Guid("4c4ebd22-f4b0-47de-a417-f0a2a127508c")] +[assembly: InternalsVisibleTo("ScriptCs.Core.Tests")] +[assembly: InternalsVisibleTo("ScriptCs.Engine.Roslyn.Tests")] \ No newline at end of file diff --git a/src/ScriptCs.Core/Properties/ScriptCs.Core.nuspec b/src/ScriptCs.Core/Properties/ScriptCs.Core.nuspec index 14817868..486e9ead 100644 --- a/src/ScriptCs.Core/Properties/ScriptCs.Core.nuspec +++ b/src/ScriptCs.Core/Properties/ScriptCs.Core.nuspec @@ -3,7 +3,7 @@ ScriptCs.Core $version$ - Glenn Block, Justin Rusbatch, Filip Wojcieszyn + Glenn Block, Filip Wojcieszyn, Justin Rusbatch, Kristian Hellang, Damian Schenkelman, Adam Ralph Glenn Block, Justin Rusbatch, Filip Wojcieszyn https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md http://scriptcs.net diff --git a/src/ScriptCs.Core/ReferenceLineProcessor.cs b/src/ScriptCs.Core/ReferenceLineProcessor.cs index 4ba36b4f..52ccc633 100644 --- a/src/ScriptCs.Core/ReferenceLineProcessor.cs +++ b/src/ScriptCs.Core/ReferenceLineProcessor.cs @@ -14,18 +14,14 @@ public class ReferenceLineProcessor : DirectiveLineProcessor, IReferenceLineProc public ReferenceLineProcessor(IFileSystem fileSystem) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + _fileSystem = fileSystem; } - protected override string DirectiveName - { - get { return "r"; } - } + protected override string DirectiveName => "r"; - protected override BehaviorAfterCode BehaviorAfterCode - { - get { return BehaviorAfterCode.Throw; } - } + protected override BehaviorAfterCode BehaviorAfterCode => BehaviorAfterCode.Throw; protected override bool ProcessLine(IFileParser parser, FileParserContext context, string line) { diff --git a/src/ScriptCs.Core/Repl.cs b/src/ScriptCs.Core/Repl.cs index 397dcd1f..9b9ddb35 100644 --- a/src/ScriptCs.Core/Repl.cs +++ b/src/ScriptCs.Core/Repl.cs @@ -1,58 +1,105 @@ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Runtime.ExceptionServices; -using Common.Logging; +using System.Linq; using ScriptCs.Contracts; namespace ScriptCs { - public class Repl : ScriptExecutor + public class Repl : ScriptExecutor, IRepl { private readonly string[] _scriptArgs; private readonly IObjectSerializer _serializer; + private readonly Printers _printers; + private readonly ILog _log; public Repl( string[] scriptArgs, IFileSystem fileSystem, IScriptEngine scriptEngine, IObjectSerializer serializer, - ILog logger, + ILogProvider logProvider, + IScriptLibraryComposer composer, IConsole console, - IFilePreProcessor filePreProcessor) : base(fileSystem, filePreProcessor, scriptEngine, logger) + IFilePreProcessor filePreProcessor, + IEnumerable replCommands, + Printers printers, + IScriptInfo scriptInfo) + : base(fileSystem, filePreProcessor, scriptEngine, logProvider, composer, scriptInfo) { + Guard.AgainstNullArgument("serializer", serializer); + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("console", console); + _scriptArgs = scriptArgs; _serializer = serializer; + _printers = printers; + _log = logProvider.ForCurrentType(); Console = console; + Commands = replCommands != null ? replCommands.Where(x => x.CommandName != null).ToDictionary(x => x.CommandName, x => x) : new Dictionary(); } public string Buffer { get; set; } public IConsole Console { get; private set; } + public Dictionary Commands { get; private set; } + public override void Terminate() { base.Terminate(); - Logger.Debug("Exiting console"); + _log.Debug("Exiting console"); Console.Exit(); } public override ScriptResult Execute(string script, params string[] scriptArgs) { Guard.AgainstNullArgument("script", script); - try { - if (script.StartsWith("#clear", StringComparison.OrdinalIgnoreCase)) - { - Console.Clear(); - return new ScriptResult(); - } - - if (script.StartsWith("#reset")) + if (script.StartsWith(":")) { - Reset(); - return new ScriptResult(); + var tokens = script.Split(' '); + if (tokens[0].Length > 1) + { + var command = Commands.FirstOrDefault(x => x.Key == tokens[0].Substring(1)); + + if (command.Value != null) + { + var argsToPass = new List(); + foreach (var argument in tokens.Skip(1)) + { + var argumentResult = ScriptEngine.Execute( + argument, _scriptArgs, References, Namespaces, ScriptPackSession); + + if (argumentResult.CompileExceptionInfo != null) + { + throw new Exception( + GetInvalidCommandArgumentMessage(argument), + argumentResult.CompileExceptionInfo.SourceException); + } + + if (argumentResult.ExecuteExceptionInfo != null) + { + throw new Exception( + GetInvalidCommandArgumentMessage(argument), + argumentResult.ExecuteExceptionInfo.SourceException); + } + + if (!argumentResult.IsCompleteSubmission) + { + throw new Exception(GetInvalidCommandArgumentMessage(argument)); + } + + argsToPass.Add(argumentResult.ReturnValue); + } + + var commandResult = command.Value.Execute(this, argsToPass.ToArray()); + return ProcessCommandResult(commandResult); + } + } } var preProcessResult = FilePreProcessor.ProcessScript(script); @@ -61,16 +108,23 @@ public override ScriptResult Execute(string script, params string[] scriptArgs) foreach (var reference in preProcessResult.References) { - var referencePath = FileSystem.GetFullPath(Path.Combine(Constants.BinFolder, reference)); + var referencePath = FileSystem.GetFullPath(Path.Combine(FileSystem.BinFolder, reference)); AddReferences(FileSystem.FileExists(referencePath) ? referencePath : reference); } Console.ForegroundColor = ConsoleColor.Cyan; - Buffer += preProcessResult.Code; + InjectScriptLibraries(FileSystem.CurrentDirectory, preProcessResult, ScriptPackSession.State); + + Buffer = (Buffer == null) + ? preProcessResult.Code + : Buffer + Environment.NewLine + preProcessResult.Code; + + var namespaces = Namespaces.Union(preProcessResult.Namespaces); + var references = References.Union(preProcessResult.References); - var result = ScriptEngine.Execute(Buffer, _scriptArgs, References, DefaultNamespaces, ScriptPackSession); - if (result == null) return new ScriptResult(); + var result = ScriptEngine.Execute(Buffer, _scriptArgs, references, namespaces, ScriptPackSession); + if (result == null) return ScriptResult.Empty; if (result.CompileExceptionInfo != null) { @@ -84,7 +138,12 @@ public override ScriptResult Execute(string script, params string[] scriptArgs) Console.WriteLine(result.ExecuteExceptionInfo.SourceException.Message); } - if (result.IsPendingClosingChar) + if (result.InvalidNamespaces.Any()) + { + RemoveNamespaces(result.InvalidNamespaces.ToArray()); + } + + if (!result.IsCompleteSubmission) { return result; } @@ -93,9 +152,7 @@ public override ScriptResult Execute(string script, params string[] scriptArgs) { Console.ForegroundColor = ConsoleColor.Yellow; - var serializedResult = _serializer.Serialize(result.ReturnValue); - - Console.WriteLine(serializedResult); + Console.WriteLine(_printers.GetStringFor(result.ReturnValue)); } Buffer = null; @@ -104,20 +161,53 @@ public override ScriptResult Execute(string script, params string[] scriptArgs) catch (FileNotFoundException fileEx) { RemoveReferences(fileEx.FileName); + Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("\r\n" + fileEx + "\r\n"); - return new ScriptResult { CompileExceptionInfo = ExceptionDispatchInfo.Capture(fileEx) }; + Console.WriteLine(Environment.NewLine + fileEx + Environment.NewLine); + + return new ScriptResult(compilationException: fileEx); } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("\r\n" + ex + "\r\n"); - return new ScriptResult { ExecuteExceptionInfo = ExceptionDispatchInfo.Capture(ex) }; + Console.WriteLine(Environment.NewLine + ex + Environment.NewLine); + + return new ScriptResult(executionException: ex); } finally { Console.ResetColor(); } } + + private static string GetInvalidCommandArgumentMessage(string argument) + { + return string.Format(CultureInfo.InvariantCulture, "Argument is not a valid expression: {0}", argument); + } + + private ScriptResult ProcessCommandResult(object commandResult) + { + Buffer = null; + + if (commandResult != null) + { + if (commandResult is ScriptResult) + { + var scriptCommandResult = commandResult as ScriptResult; + if (scriptCommandResult.ReturnValue != null) + { + Console.WriteLine(_serializer.Serialize(scriptCommandResult.ReturnValue)); + } + return scriptCommandResult; + } + + //if command has a result, print it + Console.WriteLine(_serializer.Serialize(commandResult)); + + return new ScriptResult(returnValue: commandResult); + } + + return ScriptResult.Empty; + } } } diff --git a/src/ScriptCs.Core/ReplCommands/AliasCommand.cs b/src/ScriptCs.Core/ReplCommands/AliasCommand.cs new file mode 100644 index 00000000..fd46b923 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/AliasCommand.cs @@ -0,0 +1,64 @@ +using System.Linq; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + using System; + using System.Globalization; + + public class AliasCommand : IReplCommand + { + private readonly IConsole _console; + + public AliasCommand(IConsole console) + { + Guard.AgainstNullArgument("console", console); + + _console = console; + } + + public string Description => "Allows you to alias a command with a custom name"; + + public string CommandName => "alias"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + if (args == null || args.Length != 2) + { + _console.WriteLine("You must specifiy the command name and alias, e.g. :alias \"clear\" \"cls\""); + return null; + } + + var commandName = args[0].ToString(); + var alias = args[1].ToString(); + + if (repl.Commands.Any(x => string.Equals(x.Key, alias, StringComparison.InvariantCultureIgnoreCase))) + { + var message = string.Format( + CultureInfo.InvariantCulture, + "\"{0}\" cannot be used as an alias since it is the name of an existing command.", + alias); + + _console.WriteLine(message); + return null; + } + + + if (!repl.Commands.TryGetValue(commandName, out var command)) + { + var message = string.Format( + CultureInfo.InvariantCulture, "There is no command named or aliased \"{0}\".", alias); + + _console.WriteLine(message); + return null; + } + + repl.Commands[alias] = command; + _console.WriteLine(string.Format("Aliased \"{0}\" as \"{1}\".", commandName, alias)); + + return null; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/CdCommand.cs b/src/ScriptCs.Core/ReplCommands/CdCommand.cs new file mode 100644 index 00000000..86010326 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/CdCommand.cs @@ -0,0 +1,28 @@ +using System.IO; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class CdCommand : IReplCommand + { + public string Description => "Changes the working directory to the path provided."; + + public string CommandName => "cd"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + if (args == null || args.Length == 0) + { + return null; + } + + var path = args[0].ToString(); + + repl.FileSystem.CurrentDirectory = Path.GetFullPath(Path.Combine(repl.FileSystem.CurrentDirectory, path)); + + return null; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/ClearCommand.cs b/src/ScriptCs.Core/ReplCommands/ClearCommand.cs new file mode 100644 index 00000000..a7ede226 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/ClearCommand.cs @@ -0,0 +1,26 @@ +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class ClearCommand : IReplCommand + { + private readonly IConsole _console; + + public string Description => "Clears the console window."; + + public ClearCommand(IConsole console) + { + Guard.AgainstNullArgument("console", console); + + _console = console; + } + + public string CommandName => "clear"; + + public object Execute(IRepl repl, object[] args) + { + _console.Clear(); + return null; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/CwdCommand.cs b/src/ScriptCs.Core/ReplCommands/CwdCommand.cs new file mode 100644 index 00000000..e193347d --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/CwdCommand.cs @@ -0,0 +1,41 @@ +using System; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class CwdCommand : IReplCommand + { + private readonly IConsole _console; + + public CwdCommand(IConsole console) + { + Guard.AgainstNullArgument("console", console); + + _console = console; + } + + public string Description => "Displays the current working directory."; + + public string CommandName => "cwd"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + var dir = repl.FileSystem.CurrentDirectory; + + var originalColor = _console.ForegroundColor; + _console.ForegroundColor = ConsoleColor.Yellow; + try + { + _console.WriteLine(dir); + } + finally + { + _console.ForegroundColor = originalColor; + } + + return null; + } + } +} diff --git a/src/ScriptCs.Core/ReplCommands/ExitCommand.cs b/src/ScriptCs.Core/ReplCommands/ExitCommand.cs new file mode 100644 index 00000000..81c3e6e8 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/ExitCommand.cs @@ -0,0 +1,41 @@ +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class ExitCommand : IReplCommand + { + private readonly IConsole _console; + + public string Description => "Exits the REPL"; + + public string CommandName => "exit"; + + public ExitCommand(IConsole console) + { + Guard.AgainstNullArgument("console", console); + + _console = console; + } + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + var response = string.Empty; + var responseIsValid = false; + + while (!responseIsValid) + { + response = (_console.ReadLine("Are you sure you wish to exit? (y/n):") ?? string.Empty).ToLowerInvariant(); + responseIsValid = response == "y" || response == "n"; + } + + if (response == "y") + { + repl.Terminate(); + } + + return null; + } + } +} diff --git a/src/ScriptCs.Core/ReplCommands/HelpCommand.cs b/src/ScriptCs.Core/ReplCommands/HelpCommand.cs new file mode 100644 index 00000000..508464f8 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/HelpCommand.cs @@ -0,0 +1,116 @@ +using System.Linq; +using System.Text; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class HelpCommand : IReplCommand + { + private readonly IConsole _console; + + public HelpCommand(IConsole console) + { + Guard.AgainstNullArgument("console", console); + + _console = console; + } + + public string Description => "Shows this help."; + + public string CommandName => "help"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + _console.WriteLine("\nThe following commands are available in the REPL:"); + foreach (var command in repl.Commands.OrderBy(x => x.Key)) + { + string key = string.Format(" :{0,-15} - ", command.Key); + + // make sure we have a good width for formatting purposes + int descWidth = _console.Width - key.Length - 1; + if (descWidth > 25) + { + _console.WriteLine(string.Format("{0}{1,10}", key, WrapTextToColumn(command.Value.Description, descWidth, indentWidth: key.Length))); + } + else + { + // safe-guard: just in the case we have a really long Repl Command "key" + // and a really narrow console width don't wrap the description + // note: the extra newline if to at least make somewhat readable + _console.WriteLine(string.Format("{0}{1,10}\n", key, command.Value.Description)); + } + } + _console.WriteLine(string.Empty); + + return null; + } + + /// + /// Word wrap text to specified column width. + /// + /// Unformatted text. + /// Size of the column width. + /// Indentation width when the text is wrap. The first line is not indented. + /// First line indent width. + /// Formatted text. + /// In the future, I believe this method will be moved into some sort of formatting helper class. + private string WrapTextToColumn(string text, int columnWidth, int indentWidth = 0, int initialWidth = 0) + { + // check the initial width + if ((initialWidth < 0) || (initialWidth > (indentWidth + columnWidth))) + { + throw new System.ArgumentOutOfRangeException("initialWidth"); + } + + // TODO: Add additional parameter error checking + + StringBuilder paragraph = new StringBuilder(text.Trim()); + + // add the initial space to text + paragraph.Insert(0, " ", initialWidth); + + if (paragraph.Length > (columnWidth)) + { + int pos = columnWidth; + int backSearchLimit = initialWidth; + do + { + // find a whitespace we can wrap the description line + int savedPos = pos; + while (!char.IsWhiteSpace(paragraph[pos])) + { + pos--; + + // guard against not finding a natural whitespace + // don't go below the spaces we create (indent) + if (pos < backSearchLimit) + { + pos = savedPos; + break; + } + } + + if (char.IsWhiteSpace(paragraph[pos])) + { + paragraph.Remove(pos, 1); // remove the whitespace we found + } + // inject a newline + paragraph.Insert(pos, System.Environment.NewLine); + pos += System.Environment.NewLine.Length; + paragraph.Insert(pos, " ", indentWidth); + pos += indentWidth; + + // prevent searching for whitespace to go below the spaces we put in + backSearchLimit = pos; + + pos += columnWidth; + } while (pos < paragraph.Length); + + } + + return paragraph.ToString(); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/InstallCommand.cs b/src/ScriptCs.Core/ReplCommands/InstallCommand.cs new file mode 100644 index 00000000..e668c82c --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/InstallCommand.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Runtime.Versioning; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class InstallCommand : IReplCommand + { + private readonly IPackageInstaller _packageInstaller; + private readonly IPackageAssemblyResolver _packageAssemblyResolver; + private readonly ILog _logger; + private readonly IInstallationProvider _installationProvider; + + public InstallCommand( + IPackageInstaller packageInstaller, + IPackageAssemblyResolver packageAssemblyResolver, + ILogProvider logProvider, + IInstallationProvider installationProvider) + { + Guard.AgainstNullArgument("packageInstaller", packageInstaller); + Guard.AgainstNullArgument("packageAssemblyResolver", packageAssemblyResolver); + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("installationProvider", installationProvider); + + _packageInstaller = packageInstaller; + _packageAssemblyResolver = packageAssemblyResolver; + _logger = logProvider.ForCurrentType(); + _installationProvider = installationProvider; + } + + public string Description => "Installs a Nuget package. I.e. :install "; + + public string CommandName => "install"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + if (args == null || args.Length == 0) + { + return null; + } + + string version = null; + if (args.Length >= 2) + { + version = args[1].ToString(); + } + + var allowPre = args.Length >= 3 && args[2].ToString().ToUpperInvariant() == "PRE"; + + _logger.InfoFormat("Installing {0}", args[0]); + + _installationProvider.Initialize(); + + var packageRef = new PackageReference( + args[0].ToString(), new FrameworkName(".NETFramework,Version=v4.6.1"), version); + + _packageInstaller.InstallPackages(new[] { packageRef }, allowPre); + _packageAssemblyResolver.SavePackages(); + + var dlls = _packageAssemblyResolver.GetAssemblyNames(repl.FileSystem.CurrentDirectory) + .Except(repl.References.Paths).ToArray(); + + repl.AddReferences(dlls); + + foreach (var dll in dlls) + { + _logger.InfoFormat("Added reference to {0}", dll); + } + + return null; + } + } +} diff --git a/src/ScriptCs.Core/ReplCommands/ReferencesCommand.cs b/src/ScriptCs.Core/ReplCommands/ReferencesCommand.cs new file mode 100644 index 00000000..668b40d7 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/ReferencesCommand.cs @@ -0,0 +1,21 @@ +using System.Linq; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class ReferencesCommand : IReplCommand + { + public string CommandName => "references"; + + public string Description => "Displays a list of assemblies referenced from the REPL context."; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + return repl.References != null + ? repl.References.Assemblies.Select(x => x.FullName).Union(repl.References.Paths) + : null; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/ResetCommand.cs b/src/ScriptCs.Core/ReplCommands/ResetCommand.cs new file mode 100644 index 00000000..8e68ec56 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/ResetCommand.cs @@ -0,0 +1,19 @@ +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class ResetCommand : IReplCommand + { + public string Description => "Resets the REPL state. All local variables and member definitions are cleared."; + + public string CommandName => "reset"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + repl.Reset(); + return null; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/ScriptPacksCommand.cs b/src/ScriptCs.Core/ReplCommands/ScriptPacksCommand.cs new file mode 100644 index 00000000..b5b9a08d --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/ScriptPacksCommand.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using ScriptCs.Contracts; +using ScriptCs.Extensions; + +namespace ScriptCs.ReplCommands +{ + public class ScriptPacksCommand : IReplCommand + { + private readonly IConsole _console; + + public ScriptPacksCommand(IConsole console) + { + _console = console; + } + + public string Description => "Displays information about script packs available in the REPL session"; + + public string CommandName => "scriptpacks"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + var packContexts = repl.ScriptPackSession.Contexts; + + if (packContexts.IsNullOrEmpty()) + { + PrintInColor("There are no script packs available in this REPL session"); + return null; + } + + var importedNamespaces = repl.ScriptPackSession.Namespaces.IsNullOrEmpty() ? repl.Namespaces.ToArray() : repl.Namespaces.Union(repl.ScriptPackSession.Namespaces).ToArray(); + + foreach (var packContext in packContexts) + { + var contextType = packContext.GetType(); + PrintInColor(contextType.ToString()); + + var methods = contextType.GetAllMethods(); + var properties = contextType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + + PrintMethods(methods, importedNamespaces); + PrintProperties(properties, importedNamespaces); + + _console.WriteLine(); + } + + return null; + } + + private void PrintInColor(string text) + { + var originalColor = _console.ForegroundColor; + _console.ForegroundColor = ConsoleColor.Yellow; + _console.WriteLine(text); + _console.ForegroundColor = originalColor; + } + + private void PrintMethods(IEnumerable methods, string[] importedNamespaces) + { + if (!methods.IsNullOrEmpty()) + { + _console.WriteLine("** Methods **"); + foreach (var method in methods) + { + var methodParams = method.GetParametersWithoutExtensions() + .Select(p => string.Format("{0} {1}", GetPrintableType(p.ParameterType, importedNamespaces), p.Name)); + var methodSignature = string.Format(" - {0} {1}({2})", GetPrintableType(method.ReturnType, importedNamespaces), method.Name, + string.Join(", ", methodParams)); + + _console.WriteLine(methodSignature); + } + _console.WriteLine(); + } + } + + private void PrintProperties(IEnumerable properties, string[] importedNamespaces) + { + if (!properties.IsNullOrEmpty()) + { + _console.WriteLine("** Properties **"); + foreach (var property in properties) + { + var propertyBuilder = new StringBuilder(string.Format(" - {0} {1}", GetPrintableType(property.PropertyType, importedNamespaces), property.Name)); + propertyBuilder.Append(" {"); + + if (property.GetGetMethod() != null) + { + propertyBuilder.Append(" get;"); + } + + if (property.GetSetMethod() != null) + { + propertyBuilder.Append(" set;"); + } + + propertyBuilder.Append(" }"); + + _console.WriteLine(propertyBuilder.ToString()); + } + } + } + + private string GetPrintableType(Type type, string[] importedNamespaces) + { + if (type.Name == "Void") + { + return "void"; + } + + if (type.Name == "Object") + { + return "object"; + } + + if (type.IsGenericType) + { + return BuildGeneric(type, importedNamespaces); + } + + var nullableType = Nullable.GetUnderlyingType(type); + if (nullableType != null) + { + return string.Format("{0}?", GetPrintableType(nullableType, importedNamespaces)); + } + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return "bool"; + case TypeCode.Byte: + return "byte"; + case TypeCode.Char: + return "char"; + case TypeCode.Decimal: + return "decimal"; + case TypeCode.Double: + return "double"; + case TypeCode.Int16: + return "short"; + case TypeCode.Int32: + return "int"; + case TypeCode.Int64: + return "long"; + case TypeCode.SByte: + return "sbyte"; + case TypeCode.Single: + return "Single"; + case TypeCode.String: + return "string"; + case TypeCode.UInt16: + return "UInt16"; + case TypeCode.UInt32: + return "UInt32"; + case TypeCode.UInt64: + return "UInt64"; + default: + return string.IsNullOrEmpty(type.FullName) || importedNamespaces.Contains(type.Namespace) ? type.Name : type.FullName; + } + } + + private string BuildGeneric(Type type, string[] importedNamespaces) + { + var baseName = type.Name.Substring(0, type.Name.IndexOf("`", StringComparison.Ordinal)); + var genericDefinition = new StringBuilder(string.Format("{0}<", baseName)); + var firstArgument = true; + foreach (var t in type.GetGenericArguments()) + { + if (!firstArgument) + { + genericDefinition.Append(", "); + } + genericDefinition.Append(GetPrintableType(t, importedNamespaces)); + firstArgument = false; + } + genericDefinition.Append(">"); + return genericDefinition.ToString(); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/UsingsCommand.cs b/src/ScriptCs.Core/ReplCommands/UsingsCommand.cs new file mode 100644 index 00000000..1548b551 --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/UsingsCommand.cs @@ -0,0 +1,24 @@ +using System.Linq; +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class UsingsCommand : IReplCommand + { + public string Description => "Displays a list of namespaces imported into REPL context."; + + public string CommandName => "usings"; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + var namespaces = repl.Namespaces; + + if (repl.ScriptPackSession == null || repl.ScriptPackSession.Namespaces == null || !repl.ScriptPackSession.Namespaces.Any()) + return namespaces; + + return namespaces.Union(repl.ScriptPackSession.Namespaces).OrderBy(x => x); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ReplCommands/VarsCommand.cs b/src/ScriptCs.Core/ReplCommands/VarsCommand.cs new file mode 100644 index 00000000..87594aab --- /dev/null +++ b/src/ScriptCs.Core/ReplCommands/VarsCommand.cs @@ -0,0 +1,19 @@ +using ScriptCs.Contracts; + +namespace ScriptCs.ReplCommands +{ + public class VarsCommand : IReplCommand + { + public string CommandName => "vars"; + + public string Description => "Displays a list of variables defined within the REPL, along with their types and values."; + + public object Execute(IRepl repl, object[] args) + { + Guard.AgainstNullArgument("repl", repl); + + var replEngine = repl.ScriptEngine as IReplEngine; + return replEngine != null ? replEngine.GetLocalVariables(repl.ScriptPackSession) : null; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/ScriptCs.Core.csproj b/src/ScriptCs.Core/ScriptCs.Core.csproj index e31f2c14..a83e1dc5 100644 --- a/src/ScriptCs.Core/ScriptCs.Core.csproj +++ b/src/ScriptCs.Core/ScriptCs.Core.csproj @@ -1,74 +1,17 @@ - - - + - {E590E710-E159-48E6-A3E6-1A83D3FE732C} - Library - ScriptCs - ScriptCs.Core - ..\..\ + 1.0.0 + netstandard2.0 + ScriptCs.Core + Glenn Block, Filip Wojcieszyn, Justin Rusbatch + https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md + http://scriptcs.net + http://www.gravatar.com/avatar/5c754f646971d8bc800b9d4057931938.png?s=120 + ScriptCs.Core + ScriptCs.Core is the core framework assembly for scriptcs. + roslyn csx scriptcs - - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - - - - - - - + - - - - - - - - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - - - \ No newline at end of file diff --git a/src/ScriptCs.Core/ScriptEnvironment.cs b/src/ScriptCs.Core/ScriptEnvironment.cs index 4a7cbec4..2e307fc2 100644 --- a/src/ScriptCs.Core/ScriptEnvironment.cs +++ b/src/ScriptCs.Core/ScriptEnvironment.cs @@ -1,14 +1,57 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using ScriptCs.Contracts; namespace ScriptCs { - public class ScriptEnvironment + public class ScriptEnvironment : IScriptEnvironment { - public ScriptEnvironment(string[] scriptArgs) + private readonly IConsole _console; + private readonly Printers _printers; + private readonly IScriptInfo _scriptInfo; + + public ScriptEnvironment(string[] scriptArgs, IConsole console, Printers printers, IScriptInfo scriptInfo = null ) { + _console = console; + _printers = printers; + _scriptInfo = scriptInfo; ScriptArgs = scriptArgs; } public IReadOnlyList ScriptArgs { get; private set; } + + public void AddCustomPrinter(Func printer) + { + _console.WriteLine("Adding custom printer for " + typeof(T).Name); + _printers.AddCustomPrinter(printer); + } + + public void Print(object o) + { + _console.WriteLine(_printers.GetStringFor(o)); + } + + public void Print(T o) + { + _console.WriteLine(_printers.GetStringFor(o)); + } + + public string ScriptPath => _scriptInfo.ScriptPath; + + public string[] LoadedScripts => _scriptInfo.LoadedScripts.ToArray(); + + public Assembly ScriptAssembly { get; private set; } + + private bool _initialized; + public void Initialize() + { + if (!_initialized) + { + ScriptAssembly = Assembly.GetCallingAssembly(); + _initialized = true; + } + } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Core/ScriptExecutor.cs b/src/ScriptCs.Core/ScriptExecutor.cs index 8e28fef7..499f0e8b 100644 --- a/src/ScriptCs.Core/ScriptExecutor.cs +++ b/src/ScriptCs.Core/ScriptExecutor.cs @@ -4,132 +4,157 @@ using System.IO; using System.Linq; using System.Reflection; -using Common.Logging; using ScriptCs.Contracts; namespace ScriptCs { public class ScriptExecutor : IScriptExecutor { - public static readonly string[] DefaultReferences = new[] { "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Xml", "System.Xml.Linq" }; - public static readonly string[] DefaultNamespaces = new[] { "System", "System.Collections.Generic", "System.Linq", "System.Text", "System.Threading.Tasks", "System.IO" }; + private readonly ILog _log; + + public static readonly string[] DefaultReferences = + { + "System", + "System.Core", + "System.Data", + "System.Data.DataSetExtensions", + "System.Xml", + "System.Xml.Linq", + "System.Net.Http", + "Microsoft.CSharp", + typeof(ScriptExecutor).Assembly.Location, + typeof(IScriptEnvironment).Assembly.Location + }; + + public static readonly string[] DefaultNamespaces = + { + "System", + "System.Collections.Generic", + "System.Linq", + "System.Text", + "System.Threading.Tasks", + "System.IO", + "System.Net.Http", + "System.Dynamic" + }; + + private const string ScriptLibrariesInjected = "ScriptLibrariesInjected"; public IFileSystem FileSystem { get; private set; } - + public IFilePreProcessor FilePreProcessor { get; private set; } public IScriptEngine ScriptEngine { get; private set; } - public ILog Logger { get; private set; } - public AssemblyReferences References { get; private set; } - public Collection Namespaces { get; private set; } + public IReadOnlyCollection Namespaces { get; private set; } public ScriptPackSession ScriptPackSession { get; protected set; } - public ScriptExecutor(IFileSystem fileSystem, IFilePreProcessor filePreProcessor, IScriptEngine scriptEngine, ILog logger) + public IScriptLibraryComposer ScriptLibraryComposer { get; protected set; } + + public IScriptInfo ScriptInfo { get; protected set; } + + public ScriptExecutor( + IFileSystem fileSystem, IFilePreProcessor filePreProcessor, IScriptEngine scriptEngine, ILogProvider logProvider, IScriptInfo scriptInfo) + : this(fileSystem, filePreProcessor, scriptEngine, logProvider, new NullScriptLibraryComposer(), scriptInfo) { - References = new AssemblyReferences(); - AddReferences(DefaultReferences); - Namespaces = new Collection(); - ImportNamespaces(DefaultNamespaces); + } + + public ScriptExecutor( + IFileSystem fileSystem, + IFilePreProcessor filePreProcessor, + IScriptEngine scriptEngine, + ILogProvider logProvider, + IScriptLibraryComposer composer, + IScriptInfo scriptInfo) + { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgumentProperty("fileSystem", "BinFolder", fileSystem.BinFolder); + Guard.AgainstNullArgumentProperty("fileSystem", "DllCacheFolder", fileSystem.DllCacheFolder); + Guard.AgainstNullArgument("filePreProcessor", filePreProcessor); + Guard.AgainstNullArgument("scriptEngine", scriptEngine); + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("composer", composer); + Guard.AgainstNullArgument("scriptInfo", scriptInfo); + + References = new AssemblyReferences(new[] { GetAssemblyFromName("System.Runtime") }, DefaultReferences); + Namespaces = new ReadOnlyCollection(DefaultNamespaces); FileSystem = fileSystem; FilePreProcessor = filePreProcessor; ScriptEngine = scriptEngine; - Logger = logger; + _log = logProvider.ForCurrentType(); + ScriptLibraryComposer = composer; + ScriptInfo = scriptInfo; } - public void ImportNamespaces(params string[] namespaces) + public virtual void ImportNamespaces(params string[] namespaces) { Guard.AgainstNullArgument("namespaces", namespaces); + Namespaces = new ReadOnlyCollection(Namespaces.Union(namespaces).ToArray()); + } - foreach (var @namespace in namespaces) - { - Namespaces.Add(@namespace); - } + public virtual void RemoveNamespaces(params string[] namespaces) + { + Guard.AgainstNullArgument("namespaces", namespaces); + Namespaces = new ReadOnlyCollection(Namespaces.Except(namespaces).ToArray()); } - public void AddReferences(params Assembly[] assemblies) + public virtual void AddReferences(params Assembly[] assemblies) { Guard.AgainstNullArgument("assemblies", assemblies); - foreach (var assembly in assemblies) - { - References.Assemblies.Add(assembly); - } + References = References.Union(assemblies); } - public void RemoveReferences(params Assembly[] assemblies) + public virtual void RemoveReferences(params Assembly[] assemblies) { Guard.AgainstNullArgument("assemblies", assemblies); - foreach (var assembly in assemblies) - { - References.Assemblies.Remove(assembly); - } + References = References.Except(assemblies); } - public void AddReferences(params string[] paths) + public virtual void AddReferences(params string[] paths) { Guard.AgainstNullArgument("paths", paths); - foreach (var path in paths) - { - References.PathReferences.Add(path); - } + References = References.Union(paths); } - public void RemoveReferences(params string[] paths) + public virtual void RemoveReferences(params string[] paths) { Guard.AgainstNullArgument("paths", paths); - - foreach (var path in paths) - { - References.PathReferences.Remove(path); - } - } - - public void RemoveNamespaces(params string[] namespaces) - { - Guard.AgainstNullArgument("namespaces", namespaces); - foreach (var @namespace in namespaces) - { - Namespaces.Remove(@namespace); - } + References = References.Except(paths); } - public virtual void Initialize(IEnumerable paths, IEnumerable scriptPacks, params string[] scriptArgs) + public virtual void Initialize( + IEnumerable paths, IEnumerable scriptPacks, params string[] scriptArgs) { AddReferences(paths.ToArray()); - var bin = Path.Combine(FileSystem.CurrentDirectory, Constants.BinFolder); - var cache = Path.Combine(FileSystem.CurrentDirectory, Constants.DllCacheFolder); + var bin = Path.Combine(FileSystem.CurrentDirectory, FileSystem.BinFolder); + var cache = Path.Combine(FileSystem.CurrentDirectory, FileSystem.DllCacheFolder); ScriptEngine.BaseDirectory = bin; ScriptEngine.CacheDirectory = cache; - Logger.Debug("Initializing script packs"); + _log.Debug("Initializing script packs"); var scriptPackSession = new ScriptPackSession(scriptPacks, scriptArgs); - - scriptPackSession.InitializePacks(); ScriptPackSession = scriptPackSession; + scriptPackSession.InitializePacks(); } public virtual void Reset() { - References = new AssemblyReferences(); - AddReferences(DefaultReferences); - - Namespaces.Clear(); - ImportNamespaces(DefaultNamespaces); - + References = new AssemblyReferences(DefaultReferences); + Namespaces = new ReadOnlyCollection(DefaultNamespaces); ScriptPackSession.State.Clear(); } public virtual void Terminate() { - Logger.Debug("Terminating packs"); + _log.Debug("Terminating packs"); ScriptPackSession.TerminatePacks(); } @@ -137,22 +162,103 @@ public virtual ScriptResult Execute(string script, params string[] scriptArgs) { var path = Path.IsPathRooted(script) ? script : Path.Combine(FileSystem.CurrentDirectory, script); var result = FilePreProcessor.ProcessFile(path); - References.PathReferences.UnionWith(result.References); - var namespaces = Namespaces.Union(result.Namespaces); + ScriptEngine.FileName = Path.GetFileName(path); + ScriptInfo.ScriptPath = Path.GetFullPath(path); - Logger.Debug("Starting execution in engine"); - return ScriptEngine.Execute(result.Code, scriptArgs, References, namespaces, ScriptPackSession); + return EngineExecute(Path.GetDirectoryName(path), scriptArgs, result); } public virtual ScriptResult ExecuteScript(string script, params string[] scriptArgs) { var result = FilePreProcessor.ProcessScript(script); - References.PathReferences.UnionWith(result.References); + return EngineExecute(FileSystem.CurrentDirectory, scriptArgs, result); + } + + protected internal virtual ScriptResult EngineExecute( + string workingDirectory, + string[] scriptArgs, + FilePreProcessorResult result + ) + { + InjectScriptLibraries(workingDirectory, result, ScriptPackSession.State); var namespaces = Namespaces.Union(result.Namespaces); + var references = References.Union(result.References); + ScriptInfo.ScriptPath = result.ScriptPath; + foreach (var loadedScript in result.LoadedScripts) + { + ScriptInfo.LoadedScripts.Add(loadedScript); + } + _log.Debug("Starting execution in engine"); + return ScriptEngine.Execute(result.Code, scriptArgs, references, namespaces, ScriptPackSession); + } + + protected internal virtual void InjectScriptLibraries( + string workingDirectory, + FilePreProcessorResult result, + IDictionary state + ) + { + Guard.AgainstNullArgument("result", result); + Guard.AgainstNullArgument("state", state); - Logger.Debug("Starting execution in engine"); - return ScriptEngine.Execute(result.Code, scriptArgs, References, namespaces, ScriptPackSession); + if (state.ContainsKey(ScriptLibrariesInjected)) + { + return; + } + + var scriptLibrariesPreProcessorResult = LoadScriptLibraries(workingDirectory); + + // script libraries should be injected just before the #line directive + // if there is no #line directive, they can be injected at the beginning of code + if (result.Code == null) result.Code = string.Empty; + var safeInsertIndex = result.Code.IndexOf("#line"); + if (safeInsertIndex < 0) safeInsertIndex = 0; + + if (scriptLibrariesPreProcessorResult != null) + { + result.Code = result.Code.Insert(safeInsertIndex, scriptLibrariesPreProcessorResult.Code + Environment.NewLine + + "Env.Initialize();" + Environment.NewLine); + result.References.AddRange(scriptLibrariesPreProcessorResult.References); + result.Namespaces.AddRange(scriptLibrariesPreProcessorResult.Namespaces); + } + else + { + result.Code = result.Code.Insert(safeInsertIndex, "Env.Initialize();" + Environment.NewLine); + } + + state.Add(ScriptLibrariesInjected, null); + } + + protected internal virtual FilePreProcessorResult LoadScriptLibraries(string workingDirectory) + { + if (string.IsNullOrWhiteSpace(ScriptLibraryComposer.ScriptLibrariesFile)) + { + return null; + } + + var scriptLibrariesPath = Path.Combine(workingDirectory, FileSystem.PackagesFolder, + ScriptLibraryComposer.ScriptLibrariesFile); + + if (FileSystem.FileExists(scriptLibrariesPath)) + { + _log.DebugFormat("Found Script Library at {0}", scriptLibrariesPath); + return FilePreProcessor.ProcessFile(scriptLibrariesPath); + } + + return null; + } + + private static Assembly GetAssemblyFromName(string assemblyName) + { + try + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + catch + { + return null; + } } } } \ No newline at end of file diff --git a/src/ScriptCs.Core/ScriptExecutorExtensions.cs b/src/ScriptCs.Core/ScriptExecutorExtensions.cs index 22d5d21c..5b22d80b 100644 --- a/src/ScriptCs.Core/ScriptExecutorExtensions.cs +++ b/src/ScriptCs.Core/ScriptExecutorExtensions.cs @@ -21,6 +21,8 @@ public static void ImportNamespaces(this IScriptExecutor executor, params Type[] public static void ImportNamespace(this IScriptExecutor executor) { + Guard.AgainstNullArgument("executor", executor); + executor.ImportNamespaces(typeof(T)); } @@ -36,11 +38,15 @@ public static void AddReferences(this IScriptExecutor executor, params Type[] ty public static void AddReference(this IScriptExecutor executor) { + Guard.AgainstNullArgument("executor", executor); + executor.AddReferences(typeof(T)); } public static void AddReferenceAndImportNamespaces(this IScriptExecutor executor, Type[] types) { + Guard.AgainstNullArgument("executor", executor); + executor.AddReferences(types); executor.ImportNamespaces(types); } diff --git a/src/ScriptCs.Core/ScriptHost.cs b/src/ScriptCs.Core/ScriptHost.cs index 2420ccfc..12021bde 100644 --- a/src/ScriptCs.Core/ScriptHost.cs +++ b/src/ScriptCs.Core/ScriptHost.cs @@ -1,4 +1,5 @@ -using ScriptCs.Contracts; +using System; +using ScriptCs.Contracts; namespace ScriptCs { @@ -8,15 +9,17 @@ public class ScriptHost : IScriptHost public ScriptHost(IScriptPackManager scriptPackManager, ScriptEnvironment environment) { + Guard.AgainstNullArgument("scriptPackManager", scriptPackManager); + _scriptPackManager = scriptPackManager; Env = environment; } - public ScriptEnvironment Env { get; private set; } + public IScriptEnvironment Env { get; private set; } public T Require() where T : IScriptPackContext { return _scriptPackManager.Get(); } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Core/ScriptHostFactory.cs b/src/ScriptCs.Core/ScriptHostFactory.cs index 473aaeff..3fe65ba2 100644 --- a/src/ScriptCs.Core/ScriptHostFactory.cs +++ b/src/ScriptCs.Core/ScriptHostFactory.cs @@ -1,12 +1,24 @@ -using ScriptCs.Contracts; +using System.Linq; +using ScriptCs.Contracts; namespace ScriptCs { public class ScriptHostFactory : IScriptHostFactory { + private readonly IConsole _console; + private readonly Printers _printers; + private IScriptInfo _scriptInfo; + + public ScriptHostFactory(IConsole console, Printers printers, IScriptInfo scriptInfo) + { + _console = console; + _printers = printers; + _scriptInfo = scriptInfo; + } + public IScriptHost CreateScriptHost(IScriptPackManager scriptPackManager, string[] scriptArgs) { - return new ScriptHost(scriptPackManager, new ScriptEnvironment(scriptArgs)); + return new ScriptHost(scriptPackManager, new ScriptEnvironment(scriptArgs, _console, _printers, _scriptInfo)); } } } \ No newline at end of file diff --git a/src/ScriptCs.Core/ScriptInfo.cs b/src/ScriptCs.Core/ScriptInfo.cs new file mode 100644 index 00000000..0aaa7ef0 --- /dev/null +++ b/src/ScriptCs.Core/ScriptInfo.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using ScriptCs.Contracts; + +namespace ScriptCs +{ + public class ScriptInfo : IScriptInfo + { + public ScriptInfo() + { + LoadedScripts = new List(); + } + + public string ScriptPath { get; set; } + public IList LoadedScripts { get; private set; } + } +} diff --git a/src/ScriptCs.Core/ScriptLibraryComposer.cs b/src/ScriptCs.Core/ScriptLibraryComposer.cs new file mode 100644 index 00000000..c04d0c6b --- /dev/null +++ b/src/ScriptCs.Core/ScriptLibraryComposer.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using ScriptCs.Contracts; + +namespace ScriptCs +{ + public class ScriptLibraryComposer : IScriptLibraryComposer + { + private readonly IFileSystem _fileSystem; + private readonly IFilePreProcessor _preProcessor; + private readonly IPackageContainer _packageContainer; + private readonly IPackageAssemblyResolver _packageAssemblyResolver; + private readonly ILog _logger; + + public ScriptLibraryComposer(IFileSystem fileSystem, IFilePreProcessor preProcessor, IPackageContainer packageContainer, IPackageAssemblyResolver packageAssemblyResolver, ILogProvider logProvider) + { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("preProcessor", preProcessor); + Guard.AgainstNullArgument("packageContainer", packageContainer); + Guard.AgainstNullArgument("packageAssemblyResolver", packageAssemblyResolver); + Guard.AgainstNullArgument("logProvider", logProvider); + + _fileSystem = fileSystem; + _preProcessor = preProcessor; + _packageContainer = packageContainer; + _packageAssemblyResolver = packageAssemblyResolver; + _logger = logProvider.ForCurrentType(); + } + + internal string GetMainScript(IPackageObject package) + { + var content = package.GetContentFiles() + .Where(f => f.EndsWith("main.csx", StringComparison.InvariantCultureIgnoreCase)).ToArray(); + + string script = null; + var count = content.Length; + + if (count == 1) + { + script = content[0]; + } + else if (content.Length > 1) + { + _logger.WarnFormat("Script Libraries in '{0}' ignored due to multiple Main files being present", package.FullName); + return null; + } + + if (script != null) + { + _logger.DebugFormat("Found main script: {0}", script); + } + + return script; + } + + public virtual string ScriptLibrariesFile => "ScriptLibraries.csx"; + + public void Compose(string workingDirectory, StringBuilder builder = null) + { + if (string.IsNullOrWhiteSpace(ScriptLibrariesFile)) + { + return; + } + + var packagesPath = Path.Combine(workingDirectory, _fileSystem.PackagesFolder); + var packageScriptsPath = Path.Combine(packagesPath, ScriptLibrariesFile); + if (!_fileSystem.DirectoryExists(packagesPath) || _fileSystem.FileExists(packageScriptsPath)) + { + return; + } + + if (builder == null) + { + builder = new StringBuilder(); + } + + var namespaces = new List(); + var references = new List(); + var packageReferences = _packageAssemblyResolver.GetPackages(workingDirectory); + foreach (var reference in packageReferences) + { + ProcessPackage(packagesPath, reference, builder, references, namespaces); + } + + foreach (var ns in namespaces) + { + builder.Insert(0, String.Format("using {0};{1}", ns, Environment.NewLine)); + } + + foreach (var reference in references) + { + builder.Insert(0, String.Format("#r {0}{1}", reference, Environment.NewLine)); + } + + _fileSystem.WriteToFile(packageScriptsPath, builder.ToString()); + } + + protected internal virtual void ProcessPackage( + string packagesPath, + IPackageReference reference, + StringBuilder builder, + List references, + List namespaces) + { + Guard.AgainstNullArgument("reference", reference); + Guard.AgainstNullArgument("builder", builder); + Guard.AgainstNullArgument("references", references); + Guard.AgainstNullArgument("namespaces", namespaces); + + _logger.DebugFormat("Finding package:{0}", reference.PackageId); + var package = _packageContainer.FindPackage(packagesPath, reference); + + if (package == null) + { + _logger.WarnFormat("Package missing: {0}", reference.PackageId); + return; + } + + _logger.Debug("Package found"); + var script = GetMainScript(package); + if (script != null) + { + script = Path.Combine(packagesPath, string.Format("{0}.{1}", package.Id, package.TextVersion), script); + _logger.Debug("Pre-processing script"); + var result = _preProcessor.ProcessFile(script); + var fileWithoutExtension = Path.GetFileNameWithoutExtension(script); + var classname = fileWithoutExtension.Substring(0, fileWithoutExtension.Length - 4); + _logger.DebugFormat("Created Script Libary class: {0}", classname); + var classBuilder = new StringBuilder(); + classBuilder.AppendFormat("public class {0} : ScriptCs.ScriptLibraryWrapper {{{1}", classname, Environment.NewLine); + classBuilder.AppendLine(result.Code); + classBuilder.AppendLine("}"); + var classDefinition = classBuilder.ToString(); + _logger.TraceFormat("Class definition:{0}{0}{1}", Environment.NewLine, classDefinition); + builder.Append(classDefinition); + references.AddRange(result.References); + namespaces.AddRange(result.Namespaces); + } + } + } +} diff --git a/src/ScriptCs.Core/ScriptLibraryWrapper.cs b/src/ScriptCs.Core/ScriptLibraryWrapper.cs new file mode 100644 index 00000000..88d71c23 --- /dev/null +++ b/src/ScriptCs.Core/ScriptLibraryWrapper.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ScriptCs.Contracts; + +namespace ScriptCs +{ + public abstract class ScriptLibraryWrapper + { + private static IScriptHost _scriptHost; + + internal static IScriptHost ScriptHost => _scriptHost; + + public static void SetHost(IScriptHost scriptHost) + { + _scriptHost = scriptHost; + } + + public static T Require() where T:IScriptPackContext + { + return _scriptHost.Require(); + } + + public static IScriptEnvironment Env => _scriptHost.Env; + } +} diff --git a/src/ScriptCs.Core/ScriptPackManager.cs b/src/ScriptCs.Core/ScriptPackManager.cs index c2269004..cd21946a 100644 --- a/src/ScriptCs.Core/ScriptPackManager.cs +++ b/src/ScriptCs.Core/ScriptPackManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ScriptCs.Contracts; +using ScriptCs.Exceptions; namespace ScriptCs { @@ -20,7 +21,13 @@ public ScriptPackManager(IEnumerable contexts) public TContext Get() where TContext : IScriptPackContext { - return (TContext)_contexts[typeof(TContext)]; + var key = typeof(TContext); + if (!_contexts.ContainsKey(key)) + { + throw new ScriptPackException(string.Format("Tried to resolve a script pack '{0}', but such script pack is not available in the current execution context.", key)); + } + + return (TContext)_contexts[key]; } } } \ No newline at end of file diff --git a/src/ScriptCs.Core/ScriptPackResolver.cs b/src/ScriptCs.Core/ScriptPackResolver.cs index 59747df5..00d6da11 100644 --- a/src/ScriptCs.Core/ScriptPackResolver.cs +++ b/src/ScriptCs.Core/ScriptPackResolver.cs @@ -12,9 +12,6 @@ public ScriptPackResolver(IEnumerable scriptPacks) _scriptPacks = scriptPacks; } - public IEnumerable GetPacks() - { - return _scriptPacks; - } + public IEnumerable GetPacks() => _scriptPacks; } } \ No newline at end of file diff --git a/src/ScriptCs.Core/ScriptServices.cs b/src/ScriptCs.Core/ScriptServices.cs index cd9e34ca..bdd221e9 100644 --- a/src/ScriptCs.Core/ScriptServices.cs +++ b/src/ScriptCs.Core/ScriptServices.cs @@ -1,6 +1,6 @@ -using Common.Logging; +using System; +using System.Collections.Generic; using ScriptCs.Contracts; -using ScriptCs.Package; namespace ScriptCs { @@ -8,43 +8,53 @@ public class ScriptServices { public ScriptServices( IFileSystem fileSystem, - IPackageAssemblyResolver packageAssemblyResolver, + IPackageAssemblyResolver packageAssemblyResolver, IScriptExecutor executor, + IRepl repl, IScriptEngine engine, IFilePreProcessor filePreProcessor, - IScriptPackResolver scriptPackResolver, + IScriptPackResolver scriptPackResolver, IPackageInstaller packageInstaller, IObjectSerializer objectSerializer, - ILog logger, + ILogProvider logProvider, IAssemblyResolver assemblyResolver, + IEnumerable replCommands, IConsole console = null, - IInstallationProvider installationProvider = null) + IInstallationProvider installationProvider = null, + IScriptLibraryComposer scriptLibraryComposer = null + ) { FileSystem = fileSystem; PackageAssemblyResolver = packageAssemblyResolver; Executor = executor; + Repl = repl; Engine = engine; FilePreProcessor = filePreProcessor; ScriptPackResolver = scriptPackResolver; PackageInstaller = packageInstaller; ObjectSerializer = objectSerializer; - Logger = logger; + LogProvider = logProvider; Console = console; AssemblyResolver = assemblyResolver; InstallationProvider = installationProvider; + ReplCommands = replCommands; + ScriptLibraryComposer = scriptLibraryComposer; } public IFileSystem FileSystem { get; private set; } public IPackageAssemblyResolver PackageAssemblyResolver { get; private set; } public IScriptExecutor Executor { get; private set; } + public IRepl Repl { get; private set; } public IScriptPackResolver ScriptPackResolver { get; private set; } public IPackageInstaller PackageInstaller { get; private set; } public IObjectSerializer ObjectSerializer { get; private set; } - public ILog Logger { get; private set; } + public ILogProvider LogProvider { get; private set; } public IScriptEngine Engine { get; private set; } public IFilePreProcessor FilePreProcessor { get; private set; } public IConsole Console { get; private set; } public IAssemblyResolver AssemblyResolver { get; private set; } - public IInstallationProvider InstallationProvider { get; private set; } + public IInstallationProvider InstallationProvider { get; private set; } + public IEnumerable ReplCommands { get; private set; } + public IScriptLibraryComposer ScriptLibraryComposer { get; private set; } } } diff --git a/src/ScriptCs.Core/SessionState.cs b/src/ScriptCs.Core/SessionState.cs index b557f414..67f9fff7 100644 --- a/src/ScriptCs.Core/SessionState.cs +++ b/src/ScriptCs.Core/SessionState.cs @@ -8,5 +8,7 @@ public class SessionState public T Session { get; set; } public AssemblyReferences References { get; set; } + + public HashSet Namespaces { get; set; } } } \ No newline at end of file diff --git a/src/ScriptCs.Core/ShebangLineProcessor.cs b/src/ScriptCs.Core/ShebangLineProcessor.cs new file mode 100644 index 00000000..a422f26b --- /dev/null +++ b/src/ScriptCs.Core/ShebangLineProcessor.cs @@ -0,0 +1,15 @@ +using ScriptCs.Contracts; + +namespace ScriptCs +{ + public interface IShebangLineProcessor : ILineProcessor + { + } + + public class ShebangLineProcessor : DirectiveLineProcessor, IShebangLineProcessor + { + protected override string DirectiveName => "!/usr/bin/env"; + + protected override bool ProcessLine(IFileParser parser, FileParserContext context, string line) => true; + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/StringExtensions.cs b/src/ScriptCs.Core/StringExtensions.cs new file mode 100644 index 00000000..07341764 --- /dev/null +++ b/src/ScriptCs.Core/StringExtensions.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScriptCs +{ + public static class StringExtensions + { + public static string DefineTrace(this string code) + { + return string.Format("#define TRACE{0}{1}", Environment.NewLine, code); + } + + /// + /// Split string on whitespace, but keeps string with quotes together + /// For example: :cd "\\Foo Bar" + /// :cd + /// "\\Foo Bar". + /// + /// String with or without quotes. + /// Array of strings. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", + Justification = "Guard Against Null Argument method is not need because of the if statement.", + MessageId = "0")] + public static string[] SplitQuoted(this string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + return argument.Split(' '); + } + + // count the number of quotes and throw something is not even + // the fastest way is to just loop thru the string + // http://cc.davelozinski.com/c-sharp/fastest-way-to-check-if-a-string-occurs-within-a-string + Func quoteCounterFunc = delegate (string line) + { + int count = 0; + for (int x = 0; x < line.Length; x++) + { + if (line[x] == '"') + { + count++; + } + } + return count; + }; + int quotes = quoteCounterFunc(argument); + if ((quotes % 2) != 0) + { + throw new ArgumentException("String is missing a closing quote"); + } + + List list = new List(argument.Split(' ')); + + // quoted string needs to be combine back together + if (quotes > 0 && list.Count > 0) + { + Predicate findQuoteFunc = delegate (string s) { return s.Contains("\""); }; + // create function to find string item with odd number of quotes + Func findOddQuotedItemFunc = delegate (int startingIndex) { + if (startingIndex < list.Count) + { + do + { + int quickFind = list.FindIndex(startingIndex, findQuoteFunc); + int quickCount = quoteCounterFunc(list[quickFind]); + if ((quickCount % 2) != 0) + { + return quickFind; + } + // we didn't find the quoted line we are looking for + startingIndex = quickFind + 1; + } while (startingIndex < list.Count); + } + return -1; + }; + + int index = 0; + do + { + int start = findOddQuotedItemFunc(index); + if (start > 0) + { + // we have to locate the next string with odd number of quotes + int end = findOddQuotedItemFunc(start + 1); + + string combined = string.Empty; + for (int x = start; x <= end; x++) + { + // because we split on whitespace, we have to put it back when combining + combined += list[x] + ' '; + } + list[start] = combined.TrimEnd(); // remove the extra whitespace that was added + + // removed the other parts of the combined string from the list + do + { + list.RemoveAt(end--); // from the bottom up + } while (start < end); + + // advance to next item in the adjusted list + index = start + 1; + } + else + { + break; + } + + } while (index < list.Count); + } + + return list.ToArray(); + } + + public static string UndefineTrace(this string code) + { + return string.Format("#undef TRACE{0}{1}", Environment.NewLine, code); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Core/UsingLineProcessor.cs b/src/ScriptCs.Core/UsingLineProcessor.cs index 88bcdaba..5acbad12 100644 --- a/src/ScriptCs.Core/UsingLineProcessor.cs +++ b/src/ScriptCs.Core/UsingLineProcessor.cs @@ -19,6 +19,12 @@ public bool ProcessLine(IFileParser parser, FileParserContext context, string li return false; } + // for using static, we will not extract the import into the context, but rather let it be treated as code + if (line.Contains(" static ")) + { + return false; + } + var @namespace = GetNamespace(line); if (!context.Namespaces.Contains(@namespace)) { @@ -28,10 +34,7 @@ public bool ProcessLine(IFileParser parser, FileParserContext context, string li return true; } - private static bool IsUsingLine(string line) - { - return line.Trim(' ').StartsWith(UsingString) && !line.Contains("{") && line.Contains(";"); - } + private static bool IsUsingLine(string line) => line.Trim(' ').StartsWith(UsingString) && !line.Contains("{") && line.Contains(";") && !line.Contains("="); private static string GetNamespace(string line) { diff --git a/src/ScriptCs.Core/packages.config b/src/ScriptCs.Core/packages.config deleted file mode 100644 index 1b74b263..00000000 --- a/src/ScriptCs.Core/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/RoslynScriptPersistentEngine.cs b/src/ScriptCs.Engine.Roslyn/CSharpPersistentEngine.cs similarity index 62% rename from src/ScriptCs.Engine.Roslyn/RoslynScriptPersistentEngine.cs rename to src/ScriptCs.Engine.Roslyn/CSharpPersistentEngine.cs index eb0a2a7f..380e63a7 100644 --- a/src/ScriptCs.Engine.Roslyn/RoslynScriptPersistentEngine.cs +++ b/src/ScriptCs.Engine.Roslyn/CSharpPersistentEngine.cs @@ -2,21 +2,20 @@ using System.IO; using System.Linq; using System.Reflection; - -using Common.Logging; - using ScriptCs.Contracts; namespace ScriptCs.Engine.Roslyn { - public class RoslynScriptPersistentEngine : RoslynScriptCompilerEngine + public class CSharpPersistentEngine : CSharpScriptCompilerEngine { private readonly IFileSystem _fileSystem; - private const string RoslynAssemblyNameCharacter = "ℛ"; + private readonly ILog _log; - public RoslynScriptPersistentEngine(IScriptHostFactory scriptHostFactory, ILog logger, IFileSystem fileSystem) - : base(scriptHostFactory, logger) + public CSharpPersistentEngine(IScriptHostFactory scriptHostFactory, ILogProvider logProvider, IFileSystem fileSystem) + : base(scriptHostFactory, logProvider) { + Guard.AgainstNullArgument("logProvider", logProvider); + _log = logProvider.ForCurrentType(); _fileSystem = fileSystem; } @@ -29,7 +28,7 @@ protected override bool ShouldCompile() protected override Assembly LoadAssembly(byte[] exeBytes, byte[] pdbBytes) { - this.Logger.DebugFormat("Writing assembly to {0}.", FileName); + _log.DebugFormat("Writing assembly to {0}.", FileName); if (!_fileSystem.DirectoryExists(CacheDirectory)) { @@ -39,11 +38,8 @@ protected override Assembly LoadAssembly(byte[] exeBytes, byte[] pdbBytes) var dllPath = GetDllTargetPath(); _fileSystem.WriteAllBytes(dllPath, exeBytes); - Logger.DebugFormat("Loading assembly {0}.", dllPath); - - // the assembly is automatically loaded into the AppDomain when compiled - // just need to find and return it - return AppDomain.CurrentDomain.GetAssemblies().LastOrDefault(x => x.FullName.StartsWith(RoslynAssemblyNameCharacter)); + _log.DebugFormat("Loading assembly {0}.", dllPath); + return LoadAssemblyFromCache(); } protected override Assembly LoadAssemblyFromCache() diff --git a/src/ScriptCs.Engine.Roslyn/CSharpReplEngine.cs b/src/ScriptCs.Engine.Roslyn/CSharpReplEngine.cs new file mode 100644 index 00000000..01e841ac --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/CSharpReplEngine.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Scripting; +using ScriptCs.Contracts; + +namespace ScriptCs.Engine.Roslyn +{ + public class CSharpReplEngine : CSharpScriptEngine, IReplEngine + { + public CSharpReplEngine(IScriptHostFactory scriptHostFactory, ILogProvider logProvider) + : base(scriptHostFactory, logProvider) + { + } + + public ICollection GetLocalVariables(ScriptPackSession scriptPackSession) + { + return this.GetLocalVariables(SessionKey, scriptPackSession); + } + + protected override ScriptResult Execute(string code, object globals, SessionState sessionState) + { + if (string.IsNullOrWhiteSpace(FileName) && !IsCompleteSubmission(code)) + return ScriptResult.Incomplete; + + if (sessionState.Session != null) + { + try + { + Log.Debug("Starting subsequent REPL execution"); + var result = sessionState.Session.ContinueWithAsync(code, ScriptOptions).GetAwaiter().GetResult(); + Log.Debug("Finished subsequent REPL execution"); + sessionState.Session = result; + return new ScriptResult(returnValue: result.ReturnValue); + } + catch (AggregateException ex) + { + return new ScriptResult(executionException: ex.InnerException); + } + catch (CompilationErrorException ex) + { + return new ScriptResult(compilationException: ex); + } + catch (Exception ex) + { + return new ScriptResult(executionException: ex); + } + } + + return base.Execute(code, globals, sessionState); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/CSharpScriptCompilerEngine.cs b/src/ScriptCs.Engine.Roslyn/CSharpScriptCompilerEngine.cs new file mode 100644 index 00000000..3360ddf5 --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/CSharpScriptCompilerEngine.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using ScriptCs.Contracts; +using ScriptCs.Exceptions; +using Microsoft.CodeAnalysis.Emit; + +namespace ScriptCs.Engine.Roslyn +{ + public abstract class CSharpScriptCompilerEngine : CommonScriptEngine + { + private const string CompiledScriptClass = "Submission#0"; + private const string CompiledScriptMethod = ""; + private readonly ILog _log; + + protected CSharpScriptCompilerEngine(IScriptHostFactory scriptHostFactory, ILogProvider logProvider) + : base(scriptHostFactory, logProvider) + { + Guard.AgainstNullArgument("logProvider", logProvider); + _log = logProvider.ForCurrentType(); + } + + protected abstract bool ShouldCompile(); + + protected abstract Assembly LoadAssembly(byte[] exeBytes, byte[] pdbBytes); + + protected abstract Assembly LoadAssemblyFromCache(); + + protected override ScriptResult Execute(string code, object globals, SessionState sessionState) + { + return ShouldCompile() + ? CompileAndExecute(code, globals) + : InvokeEntryPointMethod(globals, LoadAssemblyFromCache()); + } + + protected override ScriptState GetScriptState(string code, object globals) + { + return null; + } + + protected ScriptResult CompileAndExecute(string code, object globals) + { + _log.Debug("Compiling submission"); + try + { + var script = CSharpScript.Create(code, ScriptOptions, globals.GetType()); + var compilation = script.GetCompilation(); + + using (var exeStream = new MemoryStream()) + using (var pdbStream = new MemoryStream()) + { + var result = compilation.Emit(exeStream, pdbStream: pdbStream, options: new EmitOptions(). + WithDebugInformationFormat(GetPlatformSpecificDebugInformationFormat())); + + if (result.Success) + { + _log.Debug("Compilation was successful."); + + var assembly = LoadAssembly(exeStream.ToArray(), pdbStream.ToArray()); + return InvokeEntryPointMethod(globals, assembly); + } + + var errors = string.Join(Environment.NewLine, result.Diagnostics.Select(x => x.ToString())); + + _log.ErrorFormat("Error occurred when compiling: {0})", errors); + + return new ScriptResult(compilationException: new ScriptCompilationException(errors)); + } + } + catch (Exception compileException) + { + //we catch Exception rather than CompilationErrorException because there might be issues with assembly loading too + return new ScriptResult(compilationException: new ScriptCompilationException(compileException.Message, compileException)); + } + } + + private ScriptResult InvokeEntryPointMethod(object globals, Assembly assembly) + { + _log.Debug("Retrieving compiled script class (reflection)."); + + // the following line can throw NullReferenceException, if that happens it's useful to notify that an error ocurred + var type = assembly.GetType(CompiledScriptClass); + _log.Debug("Retrieving compiled script method (reflection)."); + var method = type.GetMethod(CompiledScriptMethod, BindingFlags.Static | BindingFlags.Public); + + try + { + _log.Debug("Invoking method."); + var submissionStates = new object[2]; + submissionStates[0] = globals; + var result = method.Invoke(null, new[] {submissionStates}) as Task; + return new ScriptResult(returnValue: result.GetAwaiter().GetResult()); + } + catch (Exception executeException) + { + _log.Error("An error occurred when executing the scripts."); + + var ex = executeException.InnerException ?? executeException; + return new ScriptResult(executionException: ex); + } + } + + private static DebugInformationFormat GetPlatformSpecificDebugInformationFormat() + { + // Mono, use PortablePdb + if (Type.GetType("Mono.Runtime") != null) + { + return DebugInformationFormat.PortablePdb; + } + + // otherwise standard PDB + return DebugInformationFormat.Pdb; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/CSharpScriptEngine.cs b/src/ScriptCs.Engine.Roslyn/CSharpScriptEngine.cs new file mode 100644 index 00000000..ad99e9b9 --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/CSharpScriptEngine.cs @@ -0,0 +1,35 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using ScriptCs.Contracts; + +namespace ScriptCs.Engine.Roslyn +{ + public class CSharpScriptEngine : CommonScriptEngine + { + public CSharpScriptEngine(IScriptHostFactory scriptHostFactory, ILogProvider logProvider) : base(scriptHostFactory, logProvider) + { + } + + + protected override ScriptState GetScriptState(string code, object globals) + { + return CSharpScript.RunAsync(code, ScriptOptions, globals).GetAwaiter().GetResult(); + } + + protected bool IsCompleteSubmission(string code) + { + //invalid REPL command + if (code.StartsWith(":")) + { + return true; + } + + var options = new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Parse, SourceCodeKind.Script); + + var syntaxTree = SyntaxFactory.ParseSyntaxTree(code, options: options); + return SyntaxFactory.IsCompleteSubmission(syntaxTree); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/RoslynScriptInMemoryEngine.cs b/src/ScriptCs.Engine.Roslyn/CSharpScriptInMemoryEngine.cs similarity index 61% rename from src/ScriptCs.Engine.Roslyn/RoslynScriptInMemoryEngine.cs rename to src/ScriptCs.Engine.Roslyn/CSharpScriptInMemoryEngine.cs index 7b1a21b7..c93cead6 100644 --- a/src/ScriptCs.Engine.Roslyn/RoslynScriptInMemoryEngine.cs +++ b/src/ScriptCs.Engine.Roslyn/CSharpScriptInMemoryEngine.cs @@ -1,15 +1,18 @@ using System; using System.Reflection; -using Common.Logging; using ScriptCs.Contracts; namespace ScriptCs.Engine.Roslyn { - public class RoslynScriptInMemoryEngine : RoslynScriptCompilerEngine + public class CSharpScriptInMemoryEngine : CSharpScriptCompilerEngine { - public RoslynScriptInMemoryEngine(IScriptHostFactory scriptHostFactory, ILog logger) - : base(scriptHostFactory, logger) + private readonly ILog _log; + + public CSharpScriptInMemoryEngine(IScriptHostFactory scriptHostFactory, ILogProvider logProvider) + : base(scriptHostFactory, logProvider) { + Guard.AgainstNullArgument("logProvider", logProvider); + _log = logProvider.ForCurrentType(); } protected override bool ShouldCompile() @@ -19,12 +22,12 @@ protected override bool ShouldCompile() protected override Assembly LoadAssemblyFromCache() { - throw new NotImplementedException("Reaching this point indicates a bug. The RoslynScriptInMemoryEngine should never load the assembly from the cache."); + throw new NotImplementedException("Reaching this point indicates a bug. The CSharpScriptInMemoryEngine should never load the assembly from the cache."); } protected override Assembly LoadAssembly(byte[] exeBytes, byte[] pdbBytes) { - this.Logger.Debug("Loading assembly from memory."); + _log.Debug("Loading assembly from memory."); // this is required for debugging. otherwise, the .dll is not related to the .pdb // there might be ways of doing this without "loading", haven't found one yet diff --git a/src/ScriptCs.Engine.Roslyn/CommonScriptEngine.cs b/src/ScriptCs.Engine.Roslyn/CommonScriptEngine.cs new file mode 100644 index 00000000..5c482533 --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/CommonScriptEngine.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using ScriptCs.Contracts; + +namespace ScriptCs.Engine.Roslyn +{ + // note this class is a base for future VB engine + public abstract class CommonScriptEngine : IScriptEngine + { + protected ScriptOptions ScriptOptions { get; set; } + + protected ScriptMetadataResolver ScriptMetadataResolver { get; private set; } + + private readonly IScriptHostFactory _scriptHostFactory; + protected ILog Log; + + public const string SessionKey = "Session"; + + protected CommonScriptEngine(IScriptHostFactory scriptHostFactory, ILogProvider logProvider) + { + Guard.AgainstNullArgument("logProvider", logProvider); + ScriptMetadataResolver = ScriptMetadataResolver.Default; + ScriptOptions = ScriptOptions.Default. + WithReferences(typeof(object).Assembly, typeof(TupleElementNamesAttribute).Assembly). // System.ValueTuple + WithMetadataResolver(ScriptMetadataResolver); + _scriptHostFactory = scriptHostFactory; + Log = logProvider.ForCurrentType(); + SetCSharpVersionToLatest(); + } + + public string BaseDirectory + { + get => ScriptMetadataResolver.BaseDirectory; + set => ScriptMetadataResolver = ScriptMetadataResolver.WithBaseDirectory(value); + } + + public string CacheDirectory { get; set; } + + public string FileName { get; set; } + + public string ScriptPath { get; set; } + + public ScriptResult Execute(string code, string[] scriptArgs, AssemblyReferences references, IEnumerable namespaces, ScriptPackSession scriptPackSession) + { + if (scriptPackSession == null) + { + throw new ArgumentNullException(nameof(scriptPackSession)); + } + + if (references == null) + { + throw new ArgumentNullException(nameof(references)); + } + + Log.Debug("Starting to create execution components"); + Log.Debug("Creating script host"); + + var executionReferences = new AssemblyReferences(references.Assemblies, references.Paths); + executionReferences.Union(scriptPackSession.References); + + ScriptResult scriptResult; + SessionState sessionState; + + var isFirstExecution = !scriptPackSession.State.ContainsKey(SessionKey); + + if (isFirstExecution) + { + var host = _scriptHostFactory.CreateScriptHost( + new ScriptPackManager(scriptPackSession.Contexts), scriptArgs); + + ScriptLibraryWrapper.SetHost(host); + Log.Debug("Creating session"); + + var hostType = host.GetType(); + + ScriptOptions = ScriptOptions.AddReferences(hostType.Assembly); + + var allNamespaces = namespaces.Union(scriptPackSession.Namespaces).Distinct(); + + foreach (var reference in executionReferences.Paths) + { + Log.DebugFormat("Adding reference to {0}", reference); + ScriptOptions = ScriptOptions.AddReferences(reference); + } + + foreach (var assembly in executionReferences.Assemblies) + { + Log.DebugFormat("Adding reference to {0}", assembly.FullName); + ScriptOptions = ScriptOptions.AddReferences(assembly); + } + + foreach (var @namespace in allNamespaces) + { + Log.DebugFormat("Importing namespace {0}", @namespace); + ScriptOptions = ScriptOptions.AddImports(@namespace); + } + + sessionState = new SessionState { References = executionReferences, Namespaces = new HashSet(allNamespaces) }; + scriptPackSession.State[SessionKey] = sessionState; + + scriptResult = Execute(code, host, sessionState); + } + else + { + Log.Debug("Reusing existing session"); + sessionState = (SessionState)scriptPackSession.State[SessionKey]; + + if (sessionState.References == null) + { + sessionState.References = new AssemblyReferences(); + } + + if (sessionState.Namespaces == null) + { + sessionState.Namespaces = new HashSet(); + } + + var newReferences = executionReferences.Except(sessionState.References); + + foreach (var reference in newReferences.Paths) + { + Log.DebugFormat("Adding reference to {0}", reference); + ScriptOptions = ScriptOptions.AddReferences(reference); + sessionState.References = sessionState.References.Union(new[] { reference }); + } + + foreach (var assembly in newReferences.Assemblies) + { + Log.DebugFormat("Adding reference to {0}", assembly.FullName); + ScriptOptions = ScriptOptions.AddReferences(assembly); + sessionState.References = sessionState.References.Union(new[] { assembly }); + } + + var newNamespaces = namespaces.Except(sessionState.Namespaces); + + foreach (var @namespace in newNamespaces) + { + Log.DebugFormat("Importing namespace {0}", @namespace); + ScriptOptions = ScriptOptions.AddImports(@namespace); + sessionState.Namespaces.Add(@namespace); + } + + if (string.IsNullOrWhiteSpace(code)) + { + return ScriptResult.Empty; + } + + scriptResult = Execute(code, sessionState.Session, sessionState); + } + + return scriptResult; + + //todo handle namespace failures + //https://github.com/dotnet/roslyn/issues/1012 + } + + protected virtual ScriptResult Execute(string code, object globals, SessionState sessionState) + { + try + { + Log.Debug("Starting execution"); + var result = GetScriptState(code, globals); + Log.Debug("Finished execution"); + sessionState.Session = result; + return new ScriptResult(returnValue: result.ReturnValue); + } + catch (AggregateException ex) + { + return new ScriptResult(executionException: ex.InnerException); + } + catch (CompilationErrorException ex) + { + return new ScriptResult(compilationException: ex); + } + catch (Exception ex) + { + return new ScriptResult(executionException: ex); + } + } + + protected abstract ScriptState GetScriptState(string code, object globals); + + private void SetCSharpVersionToLatest() + { + try + { + // reset default scripting mode to latest language version to enable C# 7.1 features + // this is not needed once https://github.com/dotnet/roslyn/pull/21331 ships + var csharpScriptCompilerType = typeof(CSharpScript).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScriptCompiler"); + var parseOptionsField = csharpScriptCompilerType?.GetField("s_defaultOptions", BindingFlags.Static | BindingFlags.NonPublic); + parseOptionsField?.SetValue(null, new CSharpParseOptions(LanguageVersion.Latest, kind: SourceCodeKind.Script)); + } + catch (Exception) + { + Log.Warn("Unable to set C# language version to latest, will use the default version."); + } + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/Properties/AssemblyInfo.cs b/src/ScriptCs.Engine.Roslyn/Properties/AssemblyInfo.cs deleted file mode 100644 index 75a66a88..00000000 --- a/src/ScriptCs.Engine.Roslyn/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("ScriptCs.Engine.Roslyn")] -[assembly: AssemblyDescription("ScriptCs.Engine.Roslyn provides a Roslyn-based script engine for scriptcs.")] diff --git a/src/ScriptCs.Engine.Roslyn/Properties/ScriptCs.Engine.Roslyn.nuspec b/src/ScriptCs.Engine.Roslyn/Properties/ScriptCs.Engine.Roslyn.nuspec index bd141871..e4e3ff78 100644 --- a/src/ScriptCs.Engine.Roslyn/Properties/ScriptCs.Engine.Roslyn.nuspec +++ b/src/ScriptCs.Engine.Roslyn/Properties/ScriptCs.Engine.Roslyn.nuspec @@ -3,7 +3,7 @@ ScriptCs.Engine.Roslyn $version$ - Glenn Block, Justin Rusbatch, Filip Wojcieszyn + Glenn Block, Filip Wojcieszyn, Justin Rusbatch, Kristian Hellang, Damian Schenkelman, Adam Ralph Glenn Block, Justin Rusbatch, Filip Wojcieszyn https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md http://scriptcs.net diff --git a/src/ScriptCs.Engine.Roslyn/ReplEngineExtensions.cs b/src/ScriptCs.Engine.Roslyn/ReplEngineExtensions.cs new file mode 100644 index 00000000..4b4307c4 --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/ReplEngineExtensions.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.Scripting; +using ScriptCs.Contracts; + +namespace ScriptCs.Engine.Roslyn +{ + public static class ReplEngineExtensions + { + public static ICollection GetLocalVariables(this IReplEngine replEngine, string sessionKey, + ScriptPackSession scriptPackSession) + { + if (scriptPackSession != null && scriptPackSession.State.ContainsKey(sessionKey)) + { + var sessionState = (SessionState)scriptPackSession.State[sessionKey]; + return sessionState.Session.Variables.Select(x => $"{x.Type} {x.Name}").Distinct().ToArray(); + } + + return new string[0]; + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/RoslynModule.cs b/src/ScriptCs.Engine.Roslyn/RoslynModule.cs new file mode 100644 index 00000000..4fdb0196 --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/RoslynModule.cs @@ -0,0 +1,21 @@ +using ScriptCs.Contracts; + +namespace ScriptCs.Engine.Roslyn +{ + [Module("roslyn")] + public class RoslynModule : IModule + { + public void Initialize(IModuleConfiguration config) + { + Guard.AgainstNullArgument("config", config); + + if (!config.Overrides.ContainsKey(typeof(IScriptEngine))) + { + var engineType = config.Cache ? typeof(CSharpPersistentEngine) : typeof(CSharpScriptEngine); + engineType = config.Debug ? typeof(CSharpScriptInMemoryEngine) : engineType; + engineType = config.IsRepl ? typeof(CSharpReplEngine) : engineType; + config.Overrides[typeof(IScriptEngine)] = engineType; + } + } + } +} diff --git a/src/ScriptCs.Engine.Roslyn/RoslynScriptCompilerEngine.cs b/src/ScriptCs.Engine.Roslyn/RoslynScriptCompilerEngine.cs deleted file mode 100644 index 34076778..00000000 --- a/src/ScriptCs.Engine.Roslyn/RoslynScriptCompilerEngine.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.ExceptionServices; -using Common.Logging; - -using Roslyn.Scripting; -using ScriptCs.Contracts; -using ScriptCs.Exceptions; - -namespace ScriptCs.Engine.Roslyn -{ - public abstract class RoslynScriptCompilerEngine : RoslynScriptEngine - { - protected const string CompiledScriptClass = "Submission#0"; - protected const string CompiledScriptMethod = ""; - - protected RoslynScriptCompilerEngine(IScriptHostFactory scriptHostFactory, ILog logger) - : base(scriptHostFactory, logger) - { - } - - protected abstract bool ShouldCompile(); - - protected abstract Assembly LoadAssembly(byte[] exeBytes, byte[] pdbBytes); - - protected abstract Assembly LoadAssemblyFromCache(); - - protected override ScriptResult Execute(string code, Session session) - { - Guard.AgainstNullArgument("session", session); - - var scriptResult = new ScriptResult(); - - if (ShouldCompile()) - { - CompileAndExecute(code, session, scriptResult); - } - else - { - var assembly = LoadAssemblyFromCache(); - InvokeEntryPointMethod(session, assembly, scriptResult); - } - - return scriptResult; - } - - private void CompileAndExecute(string code, Session session, ScriptResult scriptResult) - { - Submission submission = null; - - Logger.Debug("Compiling submission"); - try - { - submission = session.CompileSubmission(code); - - var exeBytes = new byte[0]; - var pdbBytes = new byte[0]; - var compileSuccess = false; - - using (var exeStream = new MemoryStream()) - using (var pdbStream = new MemoryStream()) - { - var result = submission.Compilation.Emit(exeStream, pdbStream: pdbStream); - compileSuccess = result.Success; - - if (result.Success) - { - Logger.Debug("Compilation was successful."); - exeBytes = exeStream.ToArray(); - pdbBytes = pdbStream.ToArray(); - } - else - { - var errors = string.Join(Environment.NewLine, result.Diagnostics.Select(x => x.ToString())); - Logger.ErrorFormat("Error occurred when compiling: {0})", errors); - } - } - - if (compileSuccess) - { - var assembly = LoadAssembly(exeBytes, pdbBytes); - - InvokeEntryPointMethod(session, assembly, scriptResult); - } - } - catch (Exception compileException) - { - //we catch Exception rather than CompilationErrorException because there might be issues with assembly loading too - scriptResult.CompileExceptionInfo = - ExceptionDispatchInfo.Capture(new ScriptCompilationException(compileException.Message, compileException)); - } - } - - private void InvokeEntryPointMethod(Session session, Assembly assembly, ScriptResult scriptResult) - { - Logger.Debug("Retrieving compiled script class (reflection)."); - - // the following line can throw NullReferenceException, if that happens it's useful to notify that an error ocurred - var type = assembly.GetType(CompiledScriptClass); - Logger.Debug("Retrieving compiled script method (reflection)."); - var method = type.GetMethod(CompiledScriptMethod, BindingFlags.Static | BindingFlags.Public); - - try - { - Logger.Debug("Invoking method."); - scriptResult.ReturnValue = method.Invoke(null, new[] { session }); - } - catch (Exception executeException) - { - var ex = executeException.InnerException ?? executeException; - scriptResult.ExecuteExceptionInfo = ExceptionDispatchInfo.Capture(ex); - Logger.Error("An error occurred when executing the scripts."); - } - } - } -} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/RoslynScriptEngine.cs b/src/ScriptCs.Engine.Roslyn/RoslynScriptEngine.cs deleted file mode 100644 index f1fa17a5..00000000 --- a/src/ScriptCs.Engine.Roslyn/RoslynScriptEngine.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Common.Logging; -using Roslyn.Scripting; -using Roslyn.Scripting.CSharp; - -using ScriptCs.Contracts; - -namespace ScriptCs.Engine.Roslyn -{ - using System.Runtime.ExceptionServices; - - public class RoslynScriptEngine : IScriptEngine - { - protected readonly ScriptEngine ScriptEngine; - private readonly IScriptHostFactory _scriptHostFactory; - - public const string SessionKey = "Session"; - - public RoslynScriptEngine(IScriptHostFactory scriptHostFactory, ILog logger) - { - ScriptEngine = new ScriptEngine(); - ScriptEngine.AddReference(typeof(ScriptExecutor).Assembly); - _scriptHostFactory = scriptHostFactory; - Logger = logger; - } - - protected ILog Logger { get; private set; } - - public string BaseDirectory - { - get { return ScriptEngine.BaseDirectory; } - set { ScriptEngine.BaseDirectory = value; } - } - - public string CacheDirectory { get; set; } - - public string FileName { get; set; } - - public ScriptResult Execute(string code, string[] scriptArgs, AssemblyReferences references, IEnumerable namespaces, ScriptPackSession scriptPackSession) - { - Guard.AgainstNullArgument("scriptPackSession", scriptPackSession); - Guard.AgainstNullArgument("references", references); - - Logger.Debug("Starting to create execution components"); - Logger.Debug("Creating script host"); - - references.PathReferences.UnionWith(scriptPackSession.References); - SessionState sessionState; - - if (!scriptPackSession.State.ContainsKey(SessionKey)) - { - var host = _scriptHostFactory.CreateScriptHost(new ScriptPackManager(scriptPackSession.Contexts), scriptArgs); - Logger.Debug("Creating session"); - - var hostType = host.GetType(); - ScriptEngine.AddReference(hostType.Assembly); - var session = ScriptEngine.CreateSession(host, hostType); - - foreach (var reference in references.PathReferences) - { - Logger.DebugFormat("Adding reference to {0}", reference); - session.AddReference(reference); - } - - foreach (var assembly in references.Assemblies) - { - Logger.DebugFormat("Adding reference to {0}", assembly.FullName); - session.AddReference(assembly); - } - - foreach (var @namespace in namespaces.Union(scriptPackSession.Namespaces).Distinct()) - { - Logger.DebugFormat("Importing namespace {0}", @namespace); - session.ImportNamespace(@namespace); - } - - sessionState = new SessionState { References = references, Session = session }; - scriptPackSession.State[SessionKey] = sessionState; - } - else - { - Logger.Debug("Reusing existing session"); - sessionState = (SessionState)scriptPackSession.State[SessionKey]; - - var newReferences = sessionState.References == null ? references : references.Except(sessionState.References); - foreach (var reference in newReferences.PathReferences) - { - Logger.DebugFormat("Adding reference to {0}", reference); - sessionState.Session.AddReference(reference); - } - - foreach (var assembly in newReferences.Assemblies) - { - Logger.DebugFormat("Adding reference to {0}", assembly.FullName); - sessionState.Session.AddReference(assembly); - } - - sessionState.References = newReferences; - } - - Logger.Debug("Starting execution"); - var result = Execute(code, sessionState.Session); - Logger.Debug("Finished execution"); - return result; - } - - protected virtual ScriptResult Execute(string code, Session session) - { - Guard.AgainstNullArgument("session", session); - - var result = new ScriptResult(); - try - { - var submission = session.CompileSubmission(code); - try - { - result.ReturnValue = submission.Execute(); - } - catch (Exception ex) - { - result.ExecuteExceptionInfo = ExceptionDispatchInfo.Capture(ex); - } - } - catch (Exception ex) - { - result.UpdateClosingExpectation(ex); - if (!result.IsPendingClosingChar) - { - result.CompileExceptionInfo = ExceptionDispatchInfo.Capture(ex); - } - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/ScriptCs.Engine.Roslyn.csproj b/src/ScriptCs.Engine.Roslyn/ScriptCs.Engine.Roslyn.csproj index 16ef9cc7..e88e280a 100644 --- a/src/ScriptCs.Engine.Roslyn/ScriptCs.Engine.Roslyn.csproj +++ b/src/ScriptCs.Engine.Roslyn/ScriptCs.Engine.Roslyn.csproj @@ -1,61 +1,25 @@ - - - + - {E79EC231-E27D-4057-91C9-2D001A3A8C3B} - Library - ScriptCs.Engine.Roslyn - ScriptCs.Engine.Roslyn - ..\..\ + 1.0.0 + netstandard2.0 + ScriptCs.Engine.Roslyn + Glenn Block, Filip Wojcieszyn, Justin Rusbatch + https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md + http://scriptcs.net + http://www.gravatar.com/avatar/5c754f646971d8bc800b9d4057931938.png?s=120 + ScriptCs.Engine.Roslyn + ScriptCs.Engine.Roslyn provides a Roslyn-based script engine for scriptcs. + roslyn csx script scriptcs - - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - ..\..\packages\Roslyn.Compilers.Common.1.2.20906.2\lib\net45\Roslyn.Compilers.dll - - - ..\..\packages\Roslyn.Compilers.CSharp.1.2.20906.2\lib\net45\Roslyn.Compilers.CSharp.dll - - - - - - - - + - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - Guard.cs - - - - - - + + - - + + - - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - {e590e710-e159-48e6-a3e6-1a83d3fe732c} - ScriptCs.Core - - - - \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/app.config b/src/ScriptCs.Engine.Roslyn/app.config new file mode 100644 index 00000000..d97e7d25 --- /dev/null +++ b/src/ScriptCs.Engine.Roslyn/app.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/ScriptCs.Engine.Roslyn/packages.config b/src/ScriptCs.Engine.Roslyn/packages.config deleted file mode 100644 index 21dfd7fa..00000000 --- a/src/ScriptCs.Engine.Roslyn/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ColoredConsoleLogProvider.cs b/src/ScriptCs.Hosting/ColoredConsoleLogProvider.cs new file mode 100644 index 00000000..94dd7d23 --- /dev/null +++ b/src/ScriptCs.Hosting/ColoredConsoleLogProvider.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using ScriptCs.Contracts; + +namespace ScriptCs.Hosting +{ + public class ColoredConsoleLogProvider : ILogProvider + { + private static readonly Disposable disposable = new Disposable(); + private static readonly Dictionary colors = + new Dictionary + { + { LogLevel.Fatal, ConsoleColor.Red }, + { LogLevel.Error, ConsoleColor.Red }, + { LogLevel.Warn, ConsoleColor.DarkYellow }, + { LogLevel.Info, ConsoleColor.Gray }, + { LogLevel.Debug, ConsoleColor.DarkGray }, + { LogLevel.Trace, ConsoleColor.DarkMagenta }, + }; + + private readonly LogLevel _logLevel; + private readonly IConsole _console; + + public ColoredConsoleLogProvider(LogLevel logLevel, IConsole console) + { + Guard.AgainstNullArgument("console", console); + + _logLevel = logLevel; + _console = console; + } + + public Logger GetLogger(string name) + { + return (logLevel, messageFunc, exception, formatParameters) => + Log(name, logLevel, messageFunc, exception, formatParameters); + } + + public IDisposable OpenNestedContext(string message) => disposable; + + public IDisposable OpenMappedContext(string key, string value) => disposable; + + public bool Log( + string name, LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) + { + Guard.AgainstNullArgument("formatParameters", formatParameters); + + var isEnabled = IsEnabled(logLevel); + if (!isEnabled || messageFunc == null) + { + return isEnabled; + } + + var prefix = logLevel == LogLevel.Info + ? null + : string.Concat(logLevel.ToString().ToUpperInvariant(), ": "); + + if (_logLevel == LogLevel.Debug || _logLevel == LogLevel.Trace) + { + prefix = string.Concat(prefix, "[", name, "] "); + } + + var message = string.Format(CultureInfo.InvariantCulture, messageFunc(), formatParameters); + + var suffix = string.Empty; + if (exception != null) + { + if (_logLevel == LogLevel.Debug || _logLevel == LogLevel.Trace) + { + var exceptions = new List(); + while (exception != null) + { + var exceptionString = string.Format( + CultureInfo.InvariantCulture, + "[{0}] {1}{2}{3}", + exception.GetType().FullName, + exception.Message, + Environment.NewLine, + exception.StackTrace); + + exceptions.Add(exceptionString); + exception = exception.InnerException; + } + + var divider = string.Format( + CultureInfo.InvariantCulture, "{0}{1}{0}", Environment.NewLine, "=== INNER EXCEPTION ==="); + + suffix = " " + string.Join(divider, exceptions); + } + else + { + suffix = string.Format( + CultureInfo.InvariantCulture, " [{0}] {1}", exception.GetType().Name, exception.Message); + } + } + + ConsoleColor color; + if (!colors.TryGetValue(logLevel, out color)) + { + color = ConsoleColor.White; + } + + var originalColor = _console.ForegroundColor; + _console.ForegroundColor = color; + try + { + _console.WriteLine(string.Concat(prefix, message, suffix)); + } + finally + { + _console.ForegroundColor = originalColor; + } + + return true; + } + + private bool IsEnabled(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Fatal: + return true; + case LogLevel.Error: + return + _logLevel == LogLevel.Error || + _logLevel == LogLevel.Warn || + _logLevel == LogLevel.Info || + _logLevel == LogLevel.Debug || + _logLevel == LogLevel.Trace; + case LogLevel.Warn: + return + _logLevel == LogLevel.Warn || + _logLevel == LogLevel.Info || + _logLevel == LogLevel.Debug || + _logLevel == LogLevel.Trace; + case LogLevel.Info: + return + _logLevel == LogLevel.Info || + _logLevel == LogLevel.Debug || + _logLevel == LogLevel.Trace; + case LogLevel.Debug: + return + _logLevel == LogLevel.Debug || + _logLevel == LogLevel.Trace; + case LogLevel.Trace: + return + _logLevel == LogLevel.Trace; + } + + return true; + } + + private sealed class Disposable : IDisposable + { + public void Dispose() + { + } + } + } +} diff --git a/src/ScriptCs.Hosting/DirectoryInfo.cs b/src/ScriptCs.Hosting/DirectoryInfo.cs new file mode 100644 index 00000000..dff1ed75 --- /dev/null +++ b/src/ScriptCs.Hosting/DirectoryInfo.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ScriptCs.Hosting +{ + public class DirectoryInfo + { + public DirectoryInfo() + { + Guid = Guid.NewGuid(); + Files = new List(); + Directories = new Dictionary(); + } + + public Guid Guid { get; private set; } + public string Name { get; set; } + public string Path { get; set; } + public string FullPath { get; set; } + public List Files { get; private set; } + public Dictionary Directories { get; private set; } + } +} diff --git a/src/ScriptCs.Hosting/FileConsole.cs b/src/ScriptCs.Hosting/FileConsole.cs new file mode 100644 index 00000000..984b51ed --- /dev/null +++ b/src/ScriptCs.Hosting/FileConsole.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using ScriptCs.Contracts; + +namespace ScriptCs.Hosting +{ + public class FileConsole : IConsole + { + private readonly string _path; + private readonly IConsole _innerConsole; + + public FileConsole(string path, IConsole innerConsole) + { + Guard.AgainstNullArgument("innerConsole", innerConsole); + + _path = path; + _innerConsole = innerConsole; + } + + public void Write(string value) + { + _innerConsole.Write(value); + this.Append(value); + } + + public void WriteLine() + { + _innerConsole.WriteLine(); + this.AppendLine(string.Empty); + } + + public void WriteLine(string value) + { + _innerConsole.WriteLine(value); + this.AppendLine(value); + } + + public string ReadLine(string prompt) + { + var line = _innerConsole.ReadLine(""); + this.AppendLine(line); + return line; + } + + public void Clear() + { + _innerConsole.Clear(); + } + + public void Exit() + { + _innerConsole.Exit(); + } + + public void ResetColor() + { + _innerConsole.ResetColor(); + } + + public ConsoleColor ForegroundColor + { + get => _innerConsole.ForegroundColor; + set => _innerConsole.ForegroundColor = value; + } + + public int Width => int.MaxValue; + + private void Append(string text) + { + using (var writer = new StreamWriter(_path, true)) + { + writer.Write(text); + writer.Flush(); + } + } + + private void AppendLine(string text) + { + this.Append(text + Environment.NewLine); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/IInitializationServices.cs b/src/ScriptCs.Hosting/IInitializationServices.cs index 181ae7e6..66ae6e88 100644 --- a/src/ScriptCs.Hosting/IInitializationServices.cs +++ b/src/ScriptCs.Hosting/IInitializationServices.cs @@ -1,13 +1,26 @@ -namespace ScriptCs -{ - using ScriptCs.Contracts; +using System; +using ScriptCs.Contracts; +namespace ScriptCs.Hosting +{ public interface IInitializationServices { IAssemblyResolver GetAssemblyResolver(); - + IModuleLoader GetModuleLoader(); - + IFileSystem GetFileSystem(); + + IInstallationProvider GetInstallationProvider(); + + IPackageAssemblyResolver GetPackageAssemblyResolver(); + + IPackageInstaller GetPackageInstaller(); + + ILogProvider LogProvider { get; } + + IAppDomainAssemblyResolver GetAppDomainAssemblyResolver(); + + IAssemblyUtility GetAssemblyUtility(); } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/IModuleLoader.cs b/src/ScriptCs.Hosting/IModuleLoader.cs index 401fa833..ec610e53 100644 --- a/src/ScriptCs.Hosting/IModuleLoader.cs +++ b/src/ScriptCs.Hosting/IModuleLoader.cs @@ -1,9 +1,9 @@ using ScriptCs.Contracts; -namespace ScriptCs +namespace ScriptCs.Hosting { public interface IModuleLoader { - void Load(IModuleConfiguration config, string modulePackagesPath, string extension, params string[] moduleNames); + void Load(IModuleConfiguration config, string[] modulePackagesPaths, string hostBin, string extension, params string[] moduleNames); } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/IRuntimeServices.cs b/src/ScriptCs.Hosting/IRuntimeServices.cs index 8f3cec6b..9dd18b36 100644 --- a/src/ScriptCs.Hosting/IRuntimeServices.cs +++ b/src/ScriptCs.Hosting/IRuntimeServices.cs @@ -1,6 +1,4 @@ -using Autofac; - -namespace ScriptCs +namespace ScriptCs.Hosting { public interface IRuntimeServices { diff --git a/src/ScriptCs.Hosting/IScriptServicesBuilder.cs b/src/ScriptCs.Hosting/IScriptServicesBuilder.cs index b3778064..aa179f89 100644 --- a/src/ScriptCs.Hosting/IScriptServicesBuilder.cs +++ b/src/ScriptCs.Hosting/IScriptServicesBuilder.cs @@ -1,6 +1,6 @@ using ScriptCs.Contracts; -namespace ScriptCs +namespace ScriptCs.Hosting { public interface IScriptServicesBuilder : IServiceOverrides { @@ -12,8 +12,16 @@ public interface IScriptServicesBuilder : IServiceOverrides nestedItems); + void AddGlobal(Guid projectGuid, IList nestedItems); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/IVisualStudioSolutionWriter.cs b/src/ScriptCs.Hosting/IVisualStudioSolutionWriter.cs new file mode 100644 index 00000000..c893902e --- /dev/null +++ b/src/ScriptCs.Hosting/IVisualStudioSolutionWriter.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using ScriptCs.Contracts; + +namespace ScriptCs.Contracts +{ + public interface IVisualStudioSolutionWriter + { + string WriteSolution(IFileSystem fs, string script, IVisualStudioSolution solution, IList nestedItems = null); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/InitializationServices.cs b/src/ScriptCs.Hosting/InitializationServices.cs index e0180337..8e0fa92c 100644 --- a/src/ScriptCs.Hosting/InitializationServices.cs +++ b/src/ScriptCs.Hosting/InitializationServices.cs @@ -1,36 +1,53 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Autofac; -using Common.Logging; - using ScriptCs.Contracts; using ScriptCs.Hosting.Package; -using ScriptCs.Package; -namespace ScriptCs +namespace ScriptCs.Hosting { public class InitializationServices : ScriptServicesRegistration, IInitializationServices { - public InitializationServices(ILog logger, IDictionary overrides = null) - : base(logger, overrides) + private readonly ILog _log; + + public InitializationServices(ILogProvider logProvider, IDictionary overrides = null) + : base(logProvider, overrides) { + Guard.AgainstNullArgument("logProvider", logProvider); + + _log = logProvider.ForCurrentType(); + } protected override IContainer CreateContainer() { var builder = new ContainerBuilder(); - this.Logger.Debug("Registering initialization services"); - builder.RegisterInstance(this.Logger); + _log.Debug("Registering initialization services"); + builder.RegisterInstance(this.LogProvider); builder.RegisterType().As(); + + RegisterLineProcessors(builder); + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); + return builder.Build(); } @@ -38,39 +55,67 @@ protected override IContainer CreateContainer() public IAssemblyResolver GetAssemblyResolver() { - if (_assemblyResolver == null) - { - this.Logger.Debug("Resolving AssemblyResolver"); - _assemblyResolver = Container.Resolve(); - } - - return _assemblyResolver; + return GetService(ref _assemblyResolver); } private IModuleLoader _moduleLoader; public IModuleLoader GetModuleLoader() { - if (_moduleLoader == null) - { - this.Logger.Debug("Resolving ModuleLoader"); - _moduleLoader = Container.Resolve(); - } - - return _moduleLoader; + return GetService(ref _moduleLoader); } private IFileSystem _fileSystem; - + public IFileSystem GetFileSystem() { - if (_fileSystem == null) + return GetService(ref _fileSystem); + } + + private IInstallationProvider _installationProvider; + + public IInstallationProvider GetInstallationProvider() + { + return GetService(ref _installationProvider); + } + + private IPackageAssemblyResolver _packageAssemblyResolver; + + public IPackageAssemblyResolver GetPackageAssemblyResolver() + { + return GetService(ref _packageAssemblyResolver); + } + + private IPackageInstaller _packageInstaller; + + public IPackageInstaller GetPackageInstaller() + { + return GetService(ref _packageInstaller); + } + + private IAppDomainAssemblyResolver _appDomainAssemblyResolver; + + public IAppDomainAssemblyResolver GetAppDomainAssemblyResolver() + { + return GetService(ref _appDomainAssemblyResolver); + } + + private IAssemblyUtility _assemblyUtility; + + public IAssemblyUtility GetAssemblyUtility() + { + return GetService(ref _assemblyUtility); + } + + private T GetService(ref T service ) + { + if (Equals(service,null)) { - this.Logger.Debug("Resolving FileSystem"); - _fileSystem = Container.Resolve(); + _log.Debug(string.Format("Resolving {0}", typeof(T).Name)); + service = Container.Resolve(); } - return _fileSystem; + return service; } } } diff --git a/src/ScriptCs.Hosting/LineEditor.cs b/src/ScriptCs.Hosting/LineEditor.cs new file mode 100644 index 00000000..7c2ae132 --- /dev/null +++ b/src/ScriptCs.Hosting/LineEditor.cs @@ -0,0 +1,1173 @@ +// +// getline.cs: A command line editor +// +// Authors: +// Miguel de Icaza (miguel@novell.com) +// +// Copyright 2008 Novell, Inc. +// +// Dual-licensed under the terms of the MIT X11 license or the +// Apache License 2.0 +// +// USE -define:DEMO to build this as a standalone file and test it +// +// TODO: +// Enter an error (a = 1); Notice how the prompt is in the wrong line +// This is caused by Stderr not being tracked by System.Console. +// Completion support +// Why is Thread.Interrupt not working? Currently I resort to Abort which is too much. +// +// Limitations in System.Console: +// Console needs SIGWINCH support of some sort +// Console needs a way of updating its position after things have been written +// behind its back (P/Invoke puts for example). +// System.Console needs to get the DELETE character, and report accordingly. +// + +using System; +using System.Text; +using System.IO; +using System.Threading; +using System.Reflection; + +namespace Mono.Terminal +{ + public class LineEditor + { + public class Completion + { + public string[] Result; + public string Prefix; + + public Completion(string prefix, string[] result) + { + Prefix = prefix; + Result = result; + } + } + + public delegate Completion AutoCompleteHandler(string text, int pos); + + //static StreamWriter log; + + // The text being edited. + StringBuilder text; + + // The text as it is rendered (replaces (char)1 with ^A on display for example). + StringBuilder rendered_text; + + // The prompt specified, and the prompt shown to the user. + string prompt; + string shown_prompt; + + // The current cursor position, indexes into "text", for an index + // into rendered_text, use TextToRenderPos + int cursor; + + // The row where we started displaying data. + int home_row; + + // The maximum length that has been displayed on the screen + int max_rendered; + + // If we are done editing, this breaks the interactive loop + bool done = false; + + // The thread where the Editing started taking place + Thread edit_thread; + + // Our object that tracks history + History history; + + // The contents of the kill buffer (cut/paste in Emacs parlance) + string kill_buffer = ""; + + // The string being searched for + string search; + string last_search; + + // whether we are searching (-1= reverse; 0 = no; 1 = forward) + int searching; + + // The position where we found the match. + int match_at; + + // Used to implement the Kill semantics (multiple Alt-Ds accumulate) + KeyHandler last_handler; + + delegate void KeyHandler(); + + struct Handler + { + public ConsoleKeyInfo CKI; + public KeyHandler KeyHandler; + + public Handler(ConsoleKey key, KeyHandler h) + { + CKI = new ConsoleKeyInfo((char)0, key, false, false, false); + KeyHandler = h; + } + + public Handler(char c, KeyHandler h) + { + KeyHandler = h; + // Use the "Zoom" as a flag that we only have a character. + CKI = new ConsoleKeyInfo(c, ConsoleKey.Zoom, false, false, false); + } + + public Handler(ConsoleKeyInfo cki, KeyHandler h) + { + CKI = cki; + KeyHandler = h; + } + + public static Handler Control(char c, KeyHandler h) + { + return new Handler((char)(c - 'A' + 1), h); + } + + public static Handler Alt(char c, ConsoleKey k, KeyHandler h) + { + ConsoleKeyInfo cki = new ConsoleKeyInfo((char)c, k, false, true, false); + return new Handler(cki, h); + } + } + + /// + /// Invoked when the user requests auto-completion using the tab character + /// + /// + /// The result is null for no values found, an array with a single + /// string, in that case the string should be the text to be inserted + /// for example if the word at pos is "T", the result for a completion + /// of "ToString" should be "oString", not "ToString". + /// + /// When there are multiple results, the result should be the full + /// text + /// + public AutoCompleteHandler AutoCompleteEvent; + + static Handler[] handlers; + + public LineEditor(string name) : this(name, 10) { } + + public LineEditor(string name, int histsize) + { + handlers = new Handler[] { + new Handler (ConsoleKey.Home, CmdHome), + new Handler (ConsoleKey.End, CmdEnd), + new Handler (ConsoleKey.LeftArrow, CmdLeft), + new Handler (ConsoleKey.RightArrow, CmdRight), + new Handler (ConsoleKey.UpArrow, CmdHistoryPrev), + new Handler (ConsoleKey.DownArrow, CmdHistoryNext), + new Handler (ConsoleKey.Enter, CmdDone), + new Handler (ConsoleKey.Backspace, CmdBackspace), + new Handler (ConsoleKey.Delete, CmdDeleteChar), + new Handler (ConsoleKey.Tab, CmdTabOrComplete), + + // Emacs keys + Handler.Control ('A', CmdHome), + Handler.Control ('E', CmdEnd), + Handler.Control ('B', CmdLeft), + Handler.Control ('F', CmdRight), + Handler.Control ('P', CmdHistoryPrev), + Handler.Control ('N', CmdHistoryNext), + Handler.Control ('K', CmdKillToEOF), + Handler.Control ('Y', CmdYank), + Handler.Control ('D', CmdDeleteChar), + Handler.Control ('L', CmdRefresh), + Handler.Control ('R', CmdReverseSearch), + Handler.Control ('G', delegate {} ), + Handler.Alt ('B', ConsoleKey.B, CmdBackwardWord), + Handler.Alt ('F', ConsoleKey.F, CmdForwardWord), + + Handler.Alt ('D', ConsoleKey.D, CmdDeleteWord), + Handler.Alt ((char) 8, ConsoleKey.Backspace, CmdDeleteBackword), + + // DEBUG + //Handler.Control ('T', CmdDebug), + + // quote + Handler.Control ('Q', delegate { HandleChar (Console.ReadKey (true).KeyChar); }) + }; + + rendered_text = new StringBuilder(); + text = new StringBuilder(); + + history = new History(name, histsize); + + //if (File.Exists ("log"))File.Delete ("log"); + //log = File.CreateText ("log"); + } + + void CmdDebug() + { + history.Dump(); + Console.WriteLine(); + Render(); + } + + void Render() + { + Console.Write(shown_prompt); + Console.Write(rendered_text); + + int max = System.Math.Max(rendered_text.Length + shown_prompt.Length, max_rendered); + + for (int i = rendered_text.Length + shown_prompt.Length; i < max_rendered; i++) + Console.Write(' '); + max_rendered = shown_prompt.Length + rendered_text.Length; + + // Write one more to ensure that we always wrap around properly if we are at the + // end of a line. + Console.Write(' '); + + UpdateHomeRow(max); + } + + void UpdateHomeRow(int screenpos) + { + int lines = 1 + (screenpos / Console.WindowWidth); + + home_row = Console.CursorTop - (lines - 1); + if (home_row < 0) + home_row = 0; + } + + + void RenderFrom(int pos) + { + int rpos = TextToRenderPos(pos); + int i; + + for (i = rpos; i < rendered_text.Length; i++) + Console.Write(rendered_text[i]); + + if ((shown_prompt.Length + rendered_text.Length) > max_rendered) + max_rendered = shown_prompt.Length + rendered_text.Length; + else + { + int max_extra = max_rendered - shown_prompt.Length; + for (; i < max_extra; i++) + Console.Write(' '); + } + } + + void ComputeRendered() + { + rendered_text.Length = 0; + + for (int i = 0; i < text.Length; i++) + { + int c = (int)text[i]; + if (c < 26) + { + if (c == '\t') + rendered_text.Append(" "); + else + { + rendered_text.Append('^'); + rendered_text.Append((char)(c + (int)'A' - 1)); + } + } + else + rendered_text.Append((char)c); + } + } + + int TextToRenderPos(int pos) + { + int p = 0; + + for (int i = 0; i < pos; i++) + { + int c; + + c = (int)text[i]; + + if (c < 26) + { + if (c == 9) + p += 4; + else + p += 2; + } + else + p++; + } + + return p; + } + + int TextToScreenPos(int pos) + { + return shown_prompt.Length + TextToRenderPos(pos); + } + + string Prompt + { + get => prompt; + set => prompt = value; + } + + int LineCount => (shown_prompt.Length + rendered_text.Length) / Console.WindowWidth; + + void ForceCursor(int newpos) + { + cursor = newpos; + + int actual_pos = shown_prompt.Length + TextToRenderPos(cursor); + int row = home_row + (actual_pos / Console.WindowWidth); + int col = actual_pos % Console.WindowWidth; + + if (row >= Console.BufferHeight) + row = Console.BufferHeight - 1; + Console.SetCursorPosition(col, row); + + //log.WriteLine ("Going to cursor={0} row={1} col={2} actual={3} prompt={4} ttr={5} old={6}", newpos, row, col, actual_pos, prompt.Length, TextToRenderPos (cursor), cursor); + //log.Flush (); + } + + void UpdateCursor(int newpos) + { + if (cursor == newpos) + return; + + ForceCursor(newpos); + } + + void InsertChar(char c) + { + int prev_lines = LineCount; + text = text.Insert(cursor, c); + ComputeRendered(); + if (prev_lines != LineCount) + { + + Console.SetCursorPosition(0, home_row); + Render(); + ForceCursor(++cursor); + } + else + { + RenderFrom(cursor); + ForceCursor(++cursor); + UpdateHomeRow(TextToScreenPos(cursor)); + } + } + + // + // Commands + // + void CmdDone() + { + done = true; + } + + void CmdTabOrComplete() + { + bool complete = false; + + if (AutoCompleteEvent != null) + { + if (TabAtStartCompletes) + complete = true; + else + { + for (int i = 0; i < cursor; i++) + { + if (!Char.IsWhiteSpace(text[i])) + { + complete = true; + break; + } + } + } + + if (complete) + { + Completion completion = AutoCompleteEvent(text.ToString(), cursor); + string[] completions = completion.Result; + if (completions == null) + return; + + int ncompletions = completions.Length; + if (ncompletions == 0) + return; + + if (completions.Length == 1) + { + InsertTextAtCursor(completions[0]); + } + else + { + int last = -1; + + for (int p = 0; p < completions[0].Length; p++) + { + char c = completions[0][p]; + + + for (int i = 1; i < ncompletions; i++) + { + if (completions[i].Length < p) + goto mismatch; + + if (completions[i][p] != c) + { + goto mismatch; + } + } + last = p; + } + mismatch: + if (last != -1) + { + InsertTextAtCursor(completions[0].Substring(0, last + 1)); + } + Console.WriteLine(); + foreach (string s in completions) + { + Console.Write(completion.Prefix); + Console.Write(s); + Console.Write(' '); + } + Console.WriteLine(); + Render(); + ForceCursor(cursor); + } + } + else + HandleChar('\t'); + } + else + HandleChar('t'); + } + + void CmdHome() + { + UpdateCursor(0); + } + + void CmdEnd() + { + UpdateCursor(text.Length); + } + + void CmdLeft() + { + if (cursor == 0) + return; + + UpdateCursor(cursor - 1); + } + + void CmdBackwardWord() + { + int p = WordBackward(cursor); + if (p == -1) + return; + UpdateCursor(p); + } + + void CmdForwardWord() + { + int p = WordForward(cursor); + if (p == -1) + return; + UpdateCursor(p); + } + + void CmdRight() + { + if (cursor == text.Length) + return; + + UpdateCursor(cursor + 1); + } + + void RenderAfter(int p) + { + ForceCursor(p); + RenderFrom(p); + ForceCursor(cursor); + } + + void CmdBackspace() + { + if (cursor == 0) + return; + + text.Remove(--cursor, 1); + ComputeRendered(); + RenderAfter(cursor); + } + + void CmdDeleteChar() + { + // If there is no input, this behaves like EOF + if (text.Length == 0) + { + done = true; + text = null; + Console.WriteLine(); + return; + } + + if (cursor == text.Length) + return; + text.Remove(cursor, 1); + ComputeRendered(); + RenderAfter(cursor); + } + + int WordForward(int p) + { + if (p >= text.Length) + return -1; + + int i = p; + if (Char.IsPunctuation(text[p]) || Char.IsSymbol(text[p]) || Char.IsWhiteSpace(text[p])) + { + for (; i < text.Length; i++) + { + if (Char.IsLetterOrDigit(text[i])) + break; + } + for (; i < text.Length; i++) + { + if (!Char.IsLetterOrDigit(text[i])) + break; + } + } + else + { + for (; i < text.Length; i++) + { + if (!Char.IsLetterOrDigit(text[i])) + break; + } + } + if (i != p) + return i; + return -1; + } + + int WordBackward(int p) + { + if (p == 0) + return -1; + + int i = p - 1; + if (i == 0) + return 0; + + if (Char.IsPunctuation(text[i]) || Char.IsSymbol(text[i]) || Char.IsWhiteSpace(text[i])) + { + for (; i >= 0; i--) + { + if (Char.IsLetterOrDigit(text[i])) + break; + } + for (; i >= 0; i--) + { + if (!Char.IsLetterOrDigit(text[i])) + break; + } + } + else + { + for (; i >= 0; i--) + { + if (!Char.IsLetterOrDigit(text[i])) + break; + } + } + i++; + + if (i != p) + return i; + + return -1; + } + + void CmdDeleteWord() + { + int pos = WordForward(cursor); + + if (pos == -1) + return; + + string k = text.ToString(cursor, pos - cursor); + + if (last_handler == CmdDeleteWord) + kill_buffer = kill_buffer + k; + else + kill_buffer = k; + + text.Remove(cursor, pos - cursor); + ComputeRendered(); + RenderAfter(cursor); + } + + void CmdDeleteBackword() + { + int pos = WordBackward(cursor); + if (pos == -1) + return; + + string k = text.ToString(pos, cursor - pos); + + if (last_handler == CmdDeleteBackword) + kill_buffer = k + kill_buffer; + else + kill_buffer = k; + + text.Remove(pos, cursor - pos); + ComputeRendered(); + RenderAfter(pos); + } + + // + // Adds the current line to the history if needed + // + void HistoryUpdateLine() + { + history.Update(text.ToString()); + } + + void CmdHistoryPrev() + { + if (!history.PreviousAvailable()) + return; + + HistoryUpdateLine(); + + SetText(history.Previous()); + } + + void CmdHistoryNext() + { + if (!history.NextAvailable()) + return; + + history.Update(text.ToString()); + SetText(history.Next()); + + } + + void CmdKillToEOF() + { + kill_buffer = text.ToString(cursor, text.Length - cursor); + text.Length = cursor; + ComputeRendered(); + RenderAfter(cursor); + } + + void CmdYank() + { + InsertTextAtCursor(kill_buffer); + } + + void InsertTextAtCursor(string str) + { + int prev_lines = LineCount; + text.Insert(cursor, str); + ComputeRendered(); + if (prev_lines != LineCount) + { + Console.SetCursorPosition(0, home_row); + Render(); + cursor += str.Length; + ForceCursor(cursor); + } + else + { + RenderFrom(cursor); + cursor += str.Length; + ForceCursor(cursor); + UpdateHomeRow(TextToScreenPos(cursor)); + } + } + + void SetSearchPrompt(string s) + { + SetPrompt("(reverse-i-search)`" + s + "': "); + } + + void ReverseSearch() + { + int p; + + if (cursor == text.Length) + { + // The cursor is at the end of the string + + p = text.ToString().LastIndexOf(search); + if (p != -1) + { + match_at = p; + cursor = p; + ForceCursor(cursor); + return; + } + } + else + { + // The cursor is somewhere in the middle of the string + int start = (cursor == match_at) ? cursor - 1 : cursor; + if (start != -1) + { + p = text.ToString().LastIndexOf(search, start); + if (p != -1) + { + match_at = p; + cursor = p; + ForceCursor(cursor); + return; + } + } + } + + // Need to search backwards in history + HistoryUpdateLine(); + string s = history.SearchBackward(search); + if (s != null) + { + match_at = -1; + SetText(s); + ReverseSearch(); + } + } + + void CmdReverseSearch() + { + if (searching == 0) + { + match_at = -1; + last_search = search; + searching = -1; + search = ""; + SetSearchPrompt(""); + } + else + { + if (search == "") + { + if (last_search != "" && last_search != null) + { + search = last_search; + SetSearchPrompt(search); + + ReverseSearch(); + } + return; + } + ReverseSearch(); + } + } + + void SearchAppend(char c) + { + search = search + c; + SetSearchPrompt(search); + + // + // If the new typed data still matches the current text, stay here + // + if (cursor < text.Length) + { + string r = text.ToString(cursor, text.Length - cursor); + if (r.StartsWith(search)) + return; + } + + ReverseSearch(); + } + + void CmdRefresh() + { + Console.Clear(); + max_rendered = 0; + Render(); + ForceCursor(cursor); + } + + void InterruptEdit(object sender, ConsoleCancelEventArgs a) + { + // Do not abort our program: + a.Cancel = false; + + // Interrupt the editor + edit_thread.Abort(); + } + + void HandleChar(char c) + { + if (searching != 0) + SearchAppend(c); + else + InsertChar(c); + } + + void EditLoop() + { + ConsoleKeyInfo cki; + + while (!done) + { + ConsoleModifiers mod; + + cki = Console.ReadKey(true); + if (cki.Key == ConsoleKey.Escape) + { + cki = Console.ReadKey(true); + + mod = ConsoleModifiers.Alt; + } + else + mod = cki.Modifiers; + + bool handled = false; + + foreach (Handler handler in handlers) + { + ConsoleKeyInfo t = handler.CKI; + + if (t.Key == cki.Key && t.Modifiers == mod) + { + handled = true; + handler.KeyHandler(); + last_handler = handler.KeyHandler; + break; + } + else if (t.KeyChar == cki.KeyChar && t.Key == ConsoleKey.Zoom) + { + handled = true; + handler.KeyHandler(); + last_handler = handler.KeyHandler; + break; + } + } + if (handled) + { + if (searching != 0) + { + if (last_handler != CmdReverseSearch) + { + searching = 0; + SetPrompt(prompt); + } + } + continue; + } + + if (cki.KeyChar != (char)0) + HandleChar(cki.KeyChar); + } + } + + void InitText(string initial) + { + text = new StringBuilder(initial); + ComputeRendered(); + cursor = text.Length; + Render(); + ForceCursor(cursor); + } + + void SetText(string newtext) + { + Console.SetCursorPosition(0, home_row); + InitText(newtext); + } + + void SetPrompt(string newprompt) + { + shown_prompt = newprompt; + Console.SetCursorPosition(0, home_row); + Render(); + ForceCursor(cursor); + } + + public string Edit(string prompt, string initial) + { + edit_thread = Thread.CurrentThread; + searching = 0; + Console.CancelKeyPress += InterruptEdit; + + done = false; + history.CursorToEnd(); + max_rendered = 0; + + Prompt = prompt; + shown_prompt = prompt; + InitText(initial); + history.Append(initial); + + do + { + try + { + EditLoop(); + } + catch (ThreadAbortException) + { + searching = 0; + Thread.ResetAbort(); + Console.WriteLine(); + SetPrompt(prompt); + SetText(""); + } + } while (!done); + Console.WriteLine(); + + Console.CancelKeyPress -= InterruptEdit; + + if (text == null) + { + history.Close(); + return null; + } + + string result = text.ToString(); + if (result != "") + history.Accept(result); + else + history.RemoveLast(); + + return result; + } + + public void SaveHistory() + { + if (history != null) + { + history.Close(); + } + } + + public bool TabAtStartCompletes { get; set; } + + // + // Emulates the bash-like behavior, where edits done to the + // history are recorded + // + class History + { + string[] history; + int head, tail; + int cursor, count; + string histfile; + + public History(string app, int size) + { + if (size < 1) + throw new ArgumentException("size"); + + if (app != null) + { + string dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + //Console.WriteLine (dir); + if (!Directory.Exists(dir)) + { + try + { + Directory.CreateDirectory(dir); + } + catch + { + app = null; + } + } + if (app != null) + histfile = Path.Combine(dir, app) + ".history"; + } + + history = new string[size]; + head = tail = cursor = 0; + + if (File.Exists(histfile)) + { + using (StreamReader sr = File.OpenText(histfile)) + { + string line; + + while ((line = sr.ReadLine()) != null) + { + if (line != "") + Append(line); + } + } + } + } + + public void Close() + { + if (histfile == null) + return; + + try + { + using (StreamWriter sw = File.CreateText(histfile)) + { + int start = (count == history.Length) ? head : tail; + for (int i = start; i < start + count; i++) + { + int p = i % history.Length; + sw.WriteLine(history[p]); + } + } + } + catch + { + // ignore + } + } + + // + // Appends a value to the history + // + public void Append(string s) + { + //Console.WriteLine ("APPENDING {0} head={1} tail={2}", s, head, tail); + history[head] = s; + head = (head + 1) % history.Length; + if (head == tail) + tail = (tail + 1 % history.Length); + if (count != history.Length) + count++; + //Console.WriteLine ("DONE: head={1} tail={2}", s, head, tail); + } + + // + // Updates the current cursor location with the string, + // to support editing of history items. For the current + // line to participate, an Append must be done before. + // + public void Update(string s) + { + history[cursor] = s; + } + + public void RemoveLast() + { + head = head - 1; + if (head < 0) + head = history.Length - 1; + } + + public void Accept(string s) + { + int t = head - 1; + if (t < 0) + t = history.Length - 1; + + history[t] = s; + } + + public bool PreviousAvailable() + { + //Console.WriteLine ("h={0} t={1} cursor={2}", head, tail, cursor); + if (count == 0) + return false; + int next = cursor - 1; + if (next < 0) + next = count - 1; + + if (next == head) + return false; + + return true; + } + + public bool NextAvailable() + { + if (count == 0) + return false; + int next = (cursor + 1) % history.Length; + if (next == head) + return false; + return true; + } + + + // + // Returns: a string with the previous line contents, or + // nul if there is no data in the history to move to. + // + public string Previous() + { + if (!PreviousAvailable()) + return null; + + cursor--; + if (cursor < 0) + cursor = history.Length - 1; + + return history[cursor]; + } + + public string Next() + { + if (!NextAvailable()) + return null; + + cursor = (cursor + 1) % history.Length; + return history[cursor]; + } + + public void CursorToEnd() + { + if (head == tail) + return; + + cursor = head; + } + + public void Dump() + { + Console.WriteLine("Head={0} Tail={1} Cursor={2} count={3}", head, tail, cursor, count); + for (int i = 0; i < history.Length; i++) + { + Console.WriteLine(" {0} {1}: {2}", i == cursor ? "==>" : " ", i, history[i]); + } + //log.Flush (); + } + + public string SearchBackward(string term) + { + for (int i = 0; i < count; i++) + { + int slot = cursor - i - 1; + if (slot < 0) + slot = history.Length + slot; + if (slot >= history.Length) + slot = 0; + if (history[slot] != null && history[slot].IndexOf(term) != -1) + { + cursor = slot; + return history[slot]; + } + } + + return null; + } + + } + } + +#if DEMO + class Demo { + static void Main () + { + LineEditor le = new LineEditor ("foo"); + string s; + + while ((s = le.Edit ("shell> ", "")) != null){ + Console.WriteLine ("----> [{0}]", s); + } + } + } +#endif +} diff --git a/src/ScriptCs.Hosting/LoggerConfigurator.cs b/src/ScriptCs.Hosting/LoggerConfigurator.cs deleted file mode 100644 index b376eae6..00000000 --- a/src/ScriptCs.Hosting/LoggerConfigurator.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Globalization; -using Common.Logging.Log4Net; -using log4net; -using log4net.Core; -using log4net.Layout; -using log4net.Repository.Hierarchy; -using ScriptCs.Contracts; -using ICommonLog = Common.Logging.ILog; - -namespace ScriptCs -{ - public class LoggerConfigurator : ILoggerConfigurator - { - private const string ThreadPattern = " Thread[%thread]"; - private const string Pattern = "%-5level{threadLevel}: %message%newline"; - private const string LoggerName = "scriptcs"; - - private readonly LogLevel _logLevel; - - private ICommonLog _logger; - - public LoggerConfigurator(LogLevel logLevel) - { - _logLevel = logLevel; - } - - public void Configure(IConsole console) - { - var hierarchy = (Hierarchy)LogManager.GetRepository(); - var logger = LogManager.GetLogger(LoggerName); - var consoleAppender = new ScriptConsoleAppender(console) - { - Layout = new PatternLayout(GetLogPattern(_logLevel)), - Threshold = hierarchy.LevelMap[_logLevel.ToString().ToUpper(CultureInfo.CurrentCulture)] - }; - - hierarchy.Root.AddAppender(consoleAppender); - hierarchy.Root.Level = Level.All; - hierarchy.Configured = true; - - _logger = new CodeConfigurableLog4NetLogger(logger); - } - - public ICommonLog GetLogger() - { - return _logger; - } - - private static string GetLogPattern(LogLevel logLevel) - { - switch (logLevel) - { - case LogLevel.Error: - case LogLevel.Info: - return Pattern.Replace("{threadLevel}", string.Empty); - case LogLevel.Debug: - case LogLevel.Trace: - return Pattern.Replace("{threadLevel}", ThreadPattern); - default: - throw new ArgumentOutOfRangeException("logLevel"); - } - } - - private class CodeConfigurableLog4NetLogger : Log4NetLogger - { - protected internal CodeConfigurableLog4NetLogger(ILoggerWrapper log) - : base(log) - { - } - } - } -} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ModuleConfiguration.cs b/src/ScriptCs.Hosting/ModuleConfiguration.cs index 085eb426..b2cd4302 100644 --- a/src/ScriptCs.Hosting/ModuleConfiguration.cs +++ b/src/ScriptCs.Hosting/ModuleConfiguration.cs @@ -1,27 +1,35 @@ using System; using System.Collections.Generic; - using ScriptCs.Contracts; namespace ScriptCs.Hosting { public class ModuleConfiguration : ServiceOverrides, IModuleConfiguration { - public ModuleConfiguration(bool cache, string scriptName, bool repl, LogLevel logLevel, IDictionary overrides) + public ModuleConfiguration( + bool cache, + string scriptName, + bool isRepl, + LogLevel logLevel, + bool debug, + IDictionary overrides) : base(overrides) { Cache = cache; ScriptName = scriptName; - Repl = repl; + IsRepl = isRepl; LogLevel = logLevel; + Debug = debug; } public bool Cache { get; private set; } public string ScriptName { get; private set; } - public bool Repl { get; private set; } + public bool IsRepl { get; private set; } public LogLevel LogLevel { get; private set; } + + public bool Debug { get; private set; } } } diff --git a/src/ScriptCs.Hosting/ModuleLoader.cs b/src/ScriptCs.Hosting/ModuleLoader.cs index 8f78985a..0e402b37 100644 --- a/src/ScriptCs.Hosting/ModuleLoader.cs +++ b/src/ScriptCs.Hosting/ModuleLoader.cs @@ -2,96 +2,208 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -using System.ComponentModel.Composition.Primitives; +using System.IO; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Common.Logging; - using ScriptCs.Contracts; -namespace ScriptCs +namespace ScriptCs.Hosting { public class ModuleLoader : IModuleLoader { + internal static readonly Dictionary DefaultCSharpModules = new Dictionary + { + {"roslyn", "ScriptCs.Engine.Roslyn.dll"} + }; + + internal static readonly string DefaultCSharpExtension = ".csx"; + private readonly IAssemblyResolver _resolver; private readonly ILog _logger; - private readonly Action _addToCatalog; - private readonly Func>> _getModules; + private readonly Action _addToCatalog; + private readonly Func>> _getLazyModules; + private readonly IFileSystem _fileSystem; + private readonly IAssemblyUtility _assemblyUtility; [ImportingConstructor] - public ModuleLoader(IAssemblyResolver resolver, ILog logger) : - this(resolver, logger, null, null) - { + public ModuleLoader(IAssemblyResolver resolver, ILogProvider logProvider, IFileSystem fileSystem, IAssemblyUtility assemblyUtility) : + this(resolver, logProvider, null, null, fileSystem, assemblyUtility) + { } - public ModuleLoader(IAssemblyResolver resolver, ILog logger, Action addToCatalog, Func>> getModules) + public ModuleLoader(IAssemblyResolver resolver, ILogProvider logProvider, Action addToCatalog, Func>> getLazyModules, IFileSystem fileSystem, IAssemblyUtility assemblyUtility) { - _resolver = resolver; - _logger = logger; - if (addToCatalog == null) - { - addToCatalog = (p, catalog) => catalog.Catalogs.Add(new AssemblyCatalog(p)); - } + Guard.AgainstNullArgument("logProvider", logProvider); - _addToCatalog = addToCatalog; + _resolver = resolver; + _logger = logProvider.ForCurrentType(); - if (getModules == null) + if (addToCatalog == null) { - getModules = (container) => + addToCatalog = (assembly, catalog) => { try { - return container.GetExports(); + var assemblyCatalog = new AssemblyCatalog(assembly); + catalog.Catalogs.Add(assemblyCatalog); } - catch (ReflectionTypeLoadException exception) + catch (Exception exception) { - if (exception.LoaderExceptions != null && exception.LoaderExceptions.Any()) - { - foreach (var loaderException in exception.LoaderExceptions) - { - logger.Error(string.Format("Module loader exception {0}", loaderException.Message)); - } - } - else - { - logger.Error("Module loader threw an exception", exception); - } - return Enumerable.Empty>(); + _logger.DebugFormat("Module Loader exception: {0}", exception.Message); } }; + } + + _addToCatalog = addToCatalog; + if (getLazyModules == null) + { + getLazyModules = container => container.GetExports(); } - _getModules = getModules; + _getLazyModules = getLazyModules; + _fileSystem = fileSystem; + _assemblyUtility = assemblyUtility; } - public void Load(IModuleConfiguration config, string modulePackagesPath, string extension, params string[] moduleNames) + public void Load(IModuleConfiguration config, string[] modulePackagesPaths, string hostBin, string extension, + params string[] moduleNames) { - _logger.Debug("Loading modules from: " + modulePackagesPath); - var paths = _resolver.GetAssemblyPaths(modulePackagesPath); - var catalog = new AggregateCatalog(); - foreach (var path in paths) + Guard.AgainstNullArgument("moduleNames", moduleNames); + + if (modulePackagesPaths == null) return; + + // only CSharp module needed - use fast path + if (moduleNames.Length == 1 && DefaultCSharpModules.ContainsKey(moduleNames[0]) && (string.IsNullOrWhiteSpace(extension) || extension.Equals(DefaultCSharpExtension, StringComparison.InvariantCultureIgnoreCase))) { - _addToCatalog(path, catalog); + _logger.Debug("Only CSharp module is needed - will skip module lookup"); + var csharpModuleAssembly = DefaultCSharpModules[moduleNames[0]]; + var module = _assemblyUtility.LoadFile(Path.Combine(hostBin, csharpModuleAssembly)); + + if (module != null) + { + var moduleType = module.GetExportedTypes().FirstOrDefault(f => typeof (IModule).IsAssignableFrom(f)); + + if (moduleType != null) + { + var moduleInstance = Activator.CreateInstance(moduleType) as IModule; + + if (moduleInstance != null) + { + _logger.Debug(String.Format("Initializing module: {0}", module.GetType().FullName)); + moduleInstance.Initialize(config); + return; + } + } + } } + _logger.Debug("Loading modules from: " + String.Join(", ", modulePackagesPaths.Select(i => i))); + var paths = new List(); + + AddPaths(modulePackagesPaths, hostBin, paths); + + var catalog = CreateAggregateCatalog(paths); var container = new CompositionContainer(catalog); - var lazyModules = _getModules(container); + + var lazyModules = GetLazyModules(container); + InitializeModules(config, extension, moduleNames, lazyModules); + } + + private void InitializeModules( + IModuleConfiguration config, + string extension, + IEnumerable moduleNames, + IEnumerable> lazyModules) + { var modules = lazyModules .Where(m => moduleNames.Contains(m.Metadata.Name) || - (extension != null && m.Metadata.Extensions != null && (m.Metadata.Extensions.Split(',').Contains(extension)))) + (extension != null && m.Metadata.Extensions != null && + (m.Metadata.Extensions.Split(',').Contains(extension))) || + m.Metadata.Autoload) .Select(m => m.Value); _logger.Debug("Initializing modules"); + foreach (var module in modules) { - _logger.Debug(string.Format("Initializing module:{0}", module.GetType().FullName)); + _logger.Debug(String.Format("Initializing module: {0}", module.GetType().FullName)); module.Initialize(config); } _logger.Debug("Modules initialized"); } + + private AggregateCatalog CreateAggregateCatalog(IEnumerable paths) + { + var catalog = new AggregateCatalog(); + foreach (var path in paths) + { + _logger.DebugFormat("Found assembly: {0}", path); + + try + { + if (_assemblyUtility.IsManagedAssembly(path)) + { + _logger.DebugFormat("Adding Assembly: {0} to catalog", path); + var name = _assemblyUtility.GetAssemblyName(path); + var assembly = _assemblyUtility.Load(name); + _addToCatalog(assembly, catalog); + } + else + { + _logger.DebugFormat("Skipping Adding Native Assembly {0} to catalog", path); + } + } + catch (Exception exception) + { + _logger.DebugFormat("Module Loader exception: {0}", exception.Message); + } + } + return catalog; + } + + private void AddPaths(IEnumerable modulePackagesPaths, string hostBin, List paths) + { + foreach (var modulePaths in modulePackagesPaths + .Select(modulePackagesPath => _resolver.GetAssemblyPaths(modulePackagesPath, true))) + { + paths.AddRange(modulePaths); + } + + if (hostBin != null) + { + var assemblyPaths = _fileSystem.EnumerateBinaries(hostBin, SearchOption.TopDirectoryOnly); + paths.AddRange(assemblyPaths); + } + } + + private IEnumerable> GetLazyModules(CompositionContainer container) + { + IEnumerable> lazyModules; + + try + { + lazyModules = _getLazyModules(container); + } + catch (ReflectionTypeLoadException exception) + { + if (exception.LoaderExceptions != null && exception.LoaderExceptions.Any()) + { + foreach (var loaderException in exception.LoaderExceptions) + { + _logger.DebugFormat("Module Loader exception: {0}", loaderException.Message); + } + } + else + { + _logger.DebugFormat("Module Loader exception: {0}", exception.Message); + } + + lazyModules = Enumerable.Empty>(); + } + + return lazyModules; + } } } diff --git a/src/ScriptCs.Hosting/ObjectSerializer.cs b/src/ScriptCs.Hosting/ObjectSerializer.cs index 4ae5cdb5..bc342795 100644 --- a/src/ScriptCs.Hosting/ObjectSerializer.cs +++ b/src/ScriptCs.Hosting/ObjectSerializer.cs @@ -1,21 +1,59 @@ -using Newtonsoft.Json; - +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using ScriptCs.Contracts; -namespace ScriptCs +namespace ScriptCs.Hosting { public class ObjectSerializer : IObjectSerializer { - private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + private static readonly JsonSerializerSettings settings = new JsonSerializerSettings { + Formatting = Formatting.Indented, PreserveReferencesHandling = PreserveReferencesHandling.Objects, ReferenceLoopHandling = ReferenceLoopHandling.Serialize, - MaxDepth = 4 }; public string Serialize(object value) { - return JsonConvert.SerializeObject(value, Formatting.Indented, Settings); + try + { + var writer = new JTokenWriter(); + var serializer = JsonSerializer.Create(settings); + serializer.Serialize(writer, value); + + var container = writer.Token as JContainer; + if (container != null) + { + var idProperties = container.Descendants().OfType().Where(d => d.Name == "$id").ToList(); + if (idProperties.Any()) + { + var refProperties = container.Descendants().OfType().Where(d => d.Name == "$ref").ToList(); + if (refProperties.Any()) + { + foreach (var idProperty in idProperties + .Where(idProperty => refProperties + .All(refProperty => refProperty.Value.ToString() != idProperty.Value.ToString()))) + { + idProperty.Remove(); + } + } + else + { + foreach (var idProperty in idProperties) + { + idProperty.Remove(); + } + } + } + } + + return writer.Token.ToString(); + } + catch (JsonSerializationException) + { + return string.Format("Couldn't serialize a returned instance of {0}", value.GetType()); + } } } } diff --git a/src/ScriptCs.Hosting/Package/NugetInstallationProvider.cs b/src/ScriptCs.Hosting/Package/NugetInstallationProvider.cs index 0600ee16..e4eddb98 100644 --- a/src/ScriptCs.Hosting/Package/NugetInstallationProvider.cs +++ b/src/ScriptCs.Hosting/Package/NugetInstallationProvider.cs @@ -2,12 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Common.Logging; using NuGet; - using ScriptCs.Contracts; -using ScriptCs.Package; - using IFileSystem = ScriptCs.Contracts.IFileSystem; namespace ScriptCs.Hosting.Package @@ -21,20 +17,21 @@ public class NugetInstallationProvider : IInstallationProvider private static readonly Version EmptyVersion = new Version(); - public NugetInstallationProvider(IFileSystem fileSystem, ILog logger) + public NugetInstallationProvider(IFileSystem fileSystem, ILogProvider logProvider) { Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("logProvider", logProvider); _fileSystem = fileSystem; - _logger = logger; + _logger = logProvider.ForCurrentType(); } public void Initialize() { - var path = Path.Combine(_fileSystem.CurrentDirectory, Constants.PackagesFolder); + var path = Path.Combine(_fileSystem.CurrentDirectory, _fileSystem.PackagesFolder); _repositoryUrls = GetRepositorySources(path); var remoteRepository = new AggregateRepository(PackageRepositoryFactory.Default, _repositoryUrls, true); - _manager = new PackageManager(remoteRepository, path); + _manager = new ScriptCsPackageManager(remoteRepository, path); } public IEnumerable GetRepositorySources(string path) @@ -42,9 +39,10 @@ public IEnumerable GetRepositorySources(string path) var configFileSystem = new PhysicalFileSystem(path); ISettings settings; - if (_fileSystem.FileExists(Path.Combine(_fileSystem.CurrentDirectory, Constants.NugetFile))) + var localNuGetConfigFile = Path.Combine(_fileSystem.CurrentDirectory, _fileSystem.NugetFile); + if (_fileSystem.FileExists(localNuGetConfigFile)) { - settings = new Settings(configFileSystem, Constants.NugetFile); + settings = Settings.LoadDefaultSettings(configFileSystem, localNuGetConfigFile, null); } else { @@ -57,6 +55,9 @@ public IEnumerable GetRepositorySources(string path) } var sourceProvider = new PackageSourceProvider(settings); + + HttpClient.DefaultCredentialProvider = new SettingsCredentialProvider(NullCredentialProvider.Instance, sourceProvider); + var sources = sourceProvider.LoadPackageSources().Where(i => i.IsEnabled == true); if (sources == null || !sources.Any()) @@ -67,24 +68,15 @@ public IEnumerable GetRepositorySources(string path) return sources.Select(i => i.Source); } - public bool InstallPackage(IPackageReference packageId, bool allowPreRelease = false) + public void InstallPackage(IPackageReference packageId, bool allowPreRelease = false) { Guard.AgainstNullArgument("packageId", packageId); - + var version = GetVersion(packageId); var packageName = packageId.PackageId + " " + (version == null ? string.Empty : packageId.Version.ToString()); - try - { - _manager.InstallPackage(packageId.PackageId, version, allowPrereleaseVersions: allowPreRelease, ignoreDependencies: false); - _logger.Info("Installed: " + packageName); - return true; - } - catch (Exception e) - { - _logger.Error("Installation failed: " + packageName); - _logger.Error(e.Message); - return false; - } + + _manager.InstallPackage(packageId.PackageId, version, allowPrereleaseVersions: allowPreRelease, ignoreDependencies: false); + _logger.Info("Installed: " + packageName); } private static SemanticVersion GetVersion(IPackageReference packageReference) diff --git a/src/ScriptCs.Hosting/Package/NugetMachineWideSettings.cs b/src/ScriptCs.Hosting/Package/NugetMachineWideSettings.cs index 915ce9a8..ffd6d099 100644 --- a/src/ScriptCs.Hosting/Package/NugetMachineWideSettings.cs +++ b/src/ScriptCs.Hosting/Package/NugetMachineWideSettings.cs @@ -14,12 +14,6 @@ public NugetMachineWideSettings() _settings = new Lazy>(() => NuGet.Settings.LoadMachineWideSettings(new PhysicalFileSystem(baseDirectory))); } - public IEnumerable Settings - { - get - { - return _settings.Value; - } - } + public IEnumerable Settings => _settings.Value; } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/Package/PackageContainer.cs b/src/ScriptCs.Hosting/Package/PackageContainer.cs index 5a6beb74..1dc7c8c8 100644 --- a/src/ScriptCs.Hosting/Package/PackageContainer.cs +++ b/src/ScriptCs.Hosting/Package/PackageContainer.cs @@ -1,14 +1,11 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Versioning; using NuGet; - using ScriptCs.Contracts; -using ScriptCs.Package; - using IFileSystem = ScriptCs.Contracts.IFileSystem; -using PackageReference = ScriptCs.Package.PackageReference; namespace ScriptCs.Hosting.Package { @@ -16,38 +13,64 @@ public class PackageContainer : IPackageContainer { private const string DotNetFramework = ".NETFramework"; + private const string DotNetPortable = ".NETPortable"; + private readonly IFileSystem _fileSystem; - public PackageContainer(IFileSystem fileSystem) + private readonly ILog _logger; + + public PackageContainer(IFileSystem fileSystem, ILogProvider logProvider) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("logProvider", logProvider); + _fileSystem = fileSystem; + _logger = logProvider.ForCurrentType(); } - public IEnumerable CreatePackageFile() + public void CreatePackageFile() { - var packagesFile = Path.Combine(_fileSystem.CurrentDirectory, Constants.PackagesFile); + var packagesFile = Path.Combine(_fileSystem.CurrentDirectory, _fileSystem.PackagesFile); var packageReferenceFile = new PackageReferenceFile(packagesFile); - var packagesFolder = Path.Combine(_fileSystem.CurrentDirectory, Constants.PackagesFolder); + var packagesFolder = Path.Combine(_fileSystem.CurrentDirectory, _fileSystem.PackagesFolder); var repository = new LocalPackageRepository(packagesFolder); var newestPackages = repository.GetPackages().GroupBy(p => p.Id) .Select(g => g.OrderByDescending(p => p.Version).FirstOrDefault()); + if (!newestPackages.Any()) + { + _logger.Info("No packages found!"); + return; + } + + _logger.InfoFormat("{0} {1}...", (File.Exists(packagesFile) ? "Updating" : "Creating"), _fileSystem.PackagesFile); + foreach (var package in newestPackages) { var newestFramework = GetNewestSupportedFramework(package); - packageReferenceFile.AddEntry(package.Id, package.Version, newestFramework); - if (newestFramework == null) + if (!packageReferenceFile.EntryExists(package.Id, package.Version)) { - yield return string.Format("{0}, Version {1}", package.Id, package.Version); - } - else - { - yield return string.Format("{0}, Version {1}, .NET {2}", package.Id, package.Version, newestFramework.Version); + packageReferenceFile.AddEntry(package.Id, package.Version, package.DevelopmentDependency, newestFramework); + + if (newestFramework == null) + { + _logger.InfoFormat("Added {0} (v{1}) to {2}", package.Id, package.Version, _fileSystem.PackagesFile); + } + else + { + _logger.InfoFormat("Added {0} (v{1}, .NET {2}) to {3}", package.Id, package.Version, newestFramework.Version, _fileSystem.PackagesFile); + } + + continue; } + + _logger.InfoFormat("Skipped {0} because it already exists.", package.Id); } + + _logger.InfoFormat("Successfully {0} {1}.", (File.Exists(packagesFile) ? "updated" : "created"), _fileSystem.PackagesFile); } public IPackageObject FindPackage(string path, IPackageReference packageRef) @@ -56,8 +79,8 @@ public IPackageObject FindPackage(string path, IPackageReference packageRef) var repository = new LocalPackageRepository(path); - var package = packageRef.Version != null - ? repository.FindPackage(packageRef.PackageId, new SemanticVersion(packageRef.Version, packageRef.SpecialVersion), true, true) + var package = packageRef.Version != null && !(packageRef.Version.Major == 0 && packageRef.Version.Minor == 0) + ? repository.FindPackage(packageRef.PackageId, new SemanticVersion(packageRef.Version, packageRef.SpecialVersion), true, true) : repository.FindPackage(packageRef.PackageId); return package == null ? null : new PackageObject(package, packageRef.FrameworkName); @@ -83,7 +106,7 @@ public IEnumerable FindReferences(string path) } // No packages.config, check packages folder - var packagesFolder = Path.Combine(_fileSystem.GetWorkingDirectory(path), Constants.PackagesFolder); + var packagesFolder = Path.Combine(_fileSystem.GetWorkingDirectory(path), _fileSystem.PackagesFolder); if (!_fileSystem.DirectoryExists(packagesFolder)) { yield break; @@ -99,7 +122,7 @@ public IEnumerable FindReferences(string path) foreach (var arbitraryPackage in arbitraryPackages) { - var newestFramework = GetNewestSupportedFramework(arbitraryPackage) + var newestFramework = GetNewestSupportedFramework(arbitraryPackage) ?? VersionUtility.EmptyFramework; yield return new PackageReference( @@ -113,9 +136,21 @@ public IEnumerable FindReferences(string path) private static FrameworkName GetNewestSupportedFramework(IPackage packageMetadata) { return packageMetadata.GetSupportedFrameworks() - .Where(x => x.Identifier == DotNetFramework) + .Where(IsValidFramework) .OrderByDescending(x => x.Version) .FirstOrDefault(); } + + private static bool IsValidFramework(FrameworkName frameworkName) + { + return frameworkName.Identifier == DotNetFramework + || (frameworkName.Identifier == DotNetPortable + && frameworkName.Profile.Split('+').Any(IsValidProfile)); + } + + private static bool IsValidProfile(string profile) + { + return profile == "net40" || profile == "net45" || profile == "net461" || profile == "netstandard20"; + } } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/Package/PackageInstaller.cs b/src/ScriptCs.Hosting/Package/PackageInstaller.cs index 9ecfab5a..45c20d31 100644 --- a/src/ScriptCs.Hosting/Package/PackageInstaller.cs +++ b/src/ScriptCs.Hosting/Package/PackageInstaller.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Common.Logging; using ScriptCs.Contracts; namespace ScriptCs.Hosting.Package @@ -11,20 +10,20 @@ public class PackageInstaller : IPackageInstaller private readonly IInstallationProvider _installer; private readonly ILog _logger; - public PackageInstaller(IInstallationProvider installer, ILog logger) + public PackageInstaller(IInstallationProvider installer, ILogProvider logProvider) { + Guard.AgainstNullArgument("installer", installer); + Guard.AgainstNullArgument("logProvider", logProvider); + _installer = installer; - _logger = logger; + _logger = logProvider.ForCurrentType(); } public void InstallPackages(IEnumerable packageIds, bool allowPreRelease = false) { - if (packageIds == null) - { - throw new ArgumentNullException("packageIds"); - } + Guard.AgainstNullArgument("packageIds", packageIds); - packageIds = packageIds.ToList(); + packageIds = packageIds.Where(packageId => !_installer.IsInstalled(packageId, allowPreRelease)).ToList(); if (!packageIds.Any()) { @@ -32,24 +31,24 @@ public void InstallPackages(IEnumerable packageIds, bool allo return; } - bool successful = true; + var exceptions = new List(); foreach (var packageId in packageIds) { - if (_installer.IsInstalled(packageId, allowPreRelease)) + try { - continue; + _installer.InstallPackage(packageId, allowPreRelease); } - - if(!_installer.InstallPackage(packageId, allowPreRelease)) + catch (Exception ex) { - successful = false; + _logger.ErrorException("Error installing package.", ex); + exceptions.Add(ex); } } - - if (packageIds.Count() > 1) + + if (exceptions.Any()) { - _logger.Info(successful ? "Installation successful." : "Installation unsuccessful."); + throw new AggregateException(exceptions); } } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Hosting/Package/PackageObject.cs b/src/ScriptCs.Hosting/Package/PackageObject.cs index 7325a4df..e997d3f2 100644 --- a/src/ScriptCs.Hosting/Package/PackageObject.cs +++ b/src/ScriptCs.Hosting/Package/PackageObject.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.Versioning; +using System.Text.RegularExpressions; using NuGet; using ScriptCs.Contracts; -using ScriptCs.Package; namespace ScriptCs.Hosting.Package { @@ -22,6 +23,9 @@ public PackageObject(IPackage package, FrameworkName frameworkName) Id = package.Id; Version = package.Version.Version; TextVersion = package.Version.ToString(); + FrameworkAssemblies = package.FrameworkAssemblies + .Where(x => x.SupportedFrameworks.Any(y => y == frameworkName)) + .Select(x => x.AssemblyName); var dependencies = _package.GetCompatiblePackageDependencies(frameworkName); if (dependencies != null) @@ -35,6 +39,8 @@ public PackageObject(string packageId) Id = packageId; } + public IEnumerable FrameworkAssemblies { get; private set; } + public string Id { get; private set; } public string TextVersion { get; private set; } @@ -45,10 +51,7 @@ public PackageObject(string packageId) public IEnumerable Dependencies { get; set; } - public string FullName - { - get { return Id + "." + TextVersion; } - } + public string FullName => Id + "." + TextVersion; public IEnumerable GetCompatibleDlls(FrameworkName frameworkName) { @@ -56,7 +59,37 @@ public IEnumerable GetCompatibleDlls(FrameworkName frameworkName) IEnumerable compatibleFiles; VersionUtility.TryGetCompatibleItems(frameworkName, dlls, out compatibleFiles); + // HACK: Delete unnecessary temporary NuGet files + List tempDirectories = new List(); + foreach (PhysicalPackageFile file in dlls) + { + var match = Regex.Match(((PhysicalPackageFile)dlls.First()).SourcePath, "(" + Path.Combine(Path.GetTempPath(), "nuget").Replace("\\", "\\\\") + "\\\\[^\\\\]+?)\\\\"); + if (match.Success && match.Groups.Count > 1) + { + tempDirectories.Add(match.Groups[1].Value); + } + + } + + foreach (string directory in tempDirectories.Distinct()) + { + if (Directory.Exists(directory)) + { + Directory.Delete(directory, true); + } + } + + // /HACK + return compatibleFiles != null ? compatibleFiles.Select(i => i.Path) : null; } + + public IEnumerable GetContentFiles() + { + foreach (var file in _package.GetContentFiles()) + { + yield return file.Path; + } + } } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/Package/ScriptCsPackageManager.cs b/src/ScriptCs.Hosting/Package/ScriptCsPackageManager.cs new file mode 100644 index 00000000..d060c5d6 --- /dev/null +++ b/src/ScriptCs.Hosting/Package/ScriptCsPackageManager.cs @@ -0,0 +1,26 @@ +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; +using NuGet; + +namespace ScriptCs.Hosting.Package +{ + //needed to allow forcing framework version for installation as the InstallPackage method that accepts version is protected and all other overloads force version to null! + public class ScriptCsPackageManager : PackageManager + { + public ScriptCsPackageManager(IPackageRepository sourceRepository, string path) : base(sourceRepository, path) + { + } + + public override void InstallPackage(IPackage package, bool ignoreDependencies, bool allowPrereleaseVersions) + { + base.InstallPackage(package, new FrameworkName(FrameworkUtils.FrameworkName), ignoreDependencies, allowPrereleaseVersions); + } + + public override void InstallPackage(string packageId, SemanticVersion version, bool ignoreDependencies, bool allowPrereleaseVersions) + { + var package = PackageRepositoryHelper.ResolvePackage(SourceRepository, LocalRepository, packageId, version, allowPrereleaseVersions); + base.InstallPackage(package, new FrameworkName(FrameworkUtils.FrameworkName), ignoreDependencies, allowPrereleaseVersions); + } + } +} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/Properties/AssemblyInfo.cs b/src/ScriptCs.Hosting/Properties/AssemblyInfo.cs index 27c64e7e..ab73017e 100644 --- a/src/ScriptCs.Hosting/Properties/AssemblyInfo.cs +++ b/src/ScriptCs.Hosting/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; +using System.Runtime.CompilerServices; -[assembly: AssemblyTitle("ScriptCs.Hosting")] -[assembly: AssemblyDescription("ScriptCs.Hosting provides common services necessary for hosting scriptcs in your application.")] +[assembly: InternalsVisibleTo("ScriptCs.Hosting.Tests")] \ No newline at end of file diff --git a/src/ScriptCs.Hosting/Properties/ScriptCs.Hosting.nuspec b/src/ScriptCs.Hosting/Properties/ScriptCs.Hosting.nuspec index 655f3656..3c9437f1 100644 --- a/src/ScriptCs.Hosting/Properties/ScriptCs.Hosting.nuspec +++ b/src/ScriptCs.Hosting/Properties/ScriptCs.Hosting.nuspec @@ -3,7 +3,7 @@ ScriptCs.Hosting $version$ - Glenn Block, Justin Rusbatch, Filip Wojcieszyn + Glenn Block, Filip Wojcieszyn, Justin Rusbatch, Kristian Hellang, Damian Schenkelman, Adam Ralph Glenn Block, Justin Rusbatch, Filip Wojcieszyn https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md http://scriptcs.net diff --git a/src/ScriptCs.Hosting/ReplCommands/OpenVSCommand.cs b/src/ScriptCs.Hosting/ReplCommands/OpenVSCommand.cs new file mode 100644 index 00000000..17fe4193 --- /dev/null +++ b/src/ScriptCs.Hosting/ReplCommands/OpenVSCommand.cs @@ -0,0 +1,43 @@ +using ScriptCs.Contracts; +using System; + +namespace ScriptCs.Hosting.ReplCommands +{ + public class OpenVsCommand : IReplCommand + { + private readonly IConsole _console; + private IVisualStudioSolutionWriter _writer; + + public OpenVsCommand(IConsole console, IVisualStudioSolutionWriter writer) + { + _console = console; + _writer = writer; + } + + public object Execute(IRepl repl, object[] args) + { + if (PlatformID != PlatformID.Win32NT) + { + _console.WriteLine("Requires Windows 8 or later to run"); + return null; + } + var fs = repl.FileSystem; + string arg = args.Length > 0 ? (string)args[0] : null; + var launcher = _writer.WriteSolution(fs, arg, new VisualStudioSolution()); + _console.WriteLine("Opening Visual Studio"); + LaunchSolution(launcher); + return null; + } + + protected internal virtual void LaunchSolution(string launcher) + { + System.Diagnostics.Process.Start(launcher); + } + + protected internal virtual PlatformID PlatformID => Environment.OSVersion.Platform; + + public string Description => "Opens a script to edit/debug in Visual Studio"; + + public string CommandName => "openvs"; + } +} diff --git a/src/ScriptCs.Hosting/RuntimeServices.cs b/src/ScriptCs.Hosting/RuntimeServices.cs index 6df16b37..9a07390c 100644 --- a/src/ScriptCs.Hosting/RuntimeServices.cs +++ b/src/ScriptCs.Hosting/RuntimeServices.cs @@ -1,95 +1,157 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition.Hosting; -using System.ComponentModel.Composition.Primitives; using System.Linq; using System.Reflection; using Autofac; using Autofac.Integration.Mef; -using Common.Logging; using ScriptCs.Contracts; using ScriptCs.Hosting.Package; -namespace ScriptCs +namespace ScriptCs.Hosting { public class RuntimeServices : ScriptServicesRegistration, IRuntimeServices { - private readonly IList _lineProcessors; + private readonly ILog _log; private readonly IConsole _console; private readonly Type _scriptEngineType; private readonly Type _scriptExecutorType; + private readonly Type _replType; private readonly bool _initDirectoryCatalog; private readonly IInitializationServices _initializationServices; private readonly string _scriptName; - public RuntimeServices(ILog logger, IDictionary overrides, IList lineProcessors, IConsole console, Type scriptEngineType, Type scriptExecutorType, bool initDirectoryCatalog, IInitializationServices initializationServices, string scriptName) : - base(logger, overrides) + public RuntimeServices( + ILogProvider logProvider, + IDictionary overrides, + IConsole console, + Type scriptEngineType, + Type scriptExecutorType, + Type replType, + bool initDirectoryCatalog, + IInitializationServices initializationServices, + string scriptName) + : base(logProvider, overrides) { - _lineProcessors = lineProcessors; + Guard.AgainstNullArgument("logProvider", logProvider); + + _log = logProvider.ForCurrentType(); _console = console; _scriptEngineType = scriptEngineType; _scriptExecutorType = scriptExecutorType; + _replType = replType; _initDirectoryCatalog = initDirectoryCatalog; _initializationServices = initializationServices; _scriptName = scriptName; } + internal bool InitDirectoryCatalog => _initDirectoryCatalog; + protected override IContainer CreateContainer() { var builder = new ContainerBuilder(); - this.Logger.Debug("Registering runtime services"); + _log.Debug("Registering runtime services"); - builder.RegisterInstance(this.Logger).Exported(x => x.As()); + builder.RegisterInstance(this.LogProvider).Exported(x => x.As()); builder.RegisterType(_scriptEngineType).As().SingleInstance(); builder.RegisterType(_scriptExecutorType).As().SingleInstance(); + builder.RegisterType(_replType ?? typeof(Repl)).As().SingleInstance(); builder.RegisterType().SingleInstance(); + builder.RegisterType().SingleInstance(); RegisterLineProcessors(builder); + RegisterReplCommands(builder); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); - RegisterOverrideOrDefault(builder, b => b.RegisterInstance(_console)); - - var assemblyResolver = _initializationServices.GetAssemblyResolver(); + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterInstance(_console)); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault( + builder, b => b.RegisterType().As().SingleInstance()); + + RegisterOverrideOrDefault(builder, b => b.RegisterType().As().SingleInstance()); if (_initDirectoryCatalog) { - var currentDirectory = Environment.CurrentDirectory; - var assemblies = assemblyResolver.GetAssemblyPaths(currentDirectory); + var fileSystem = _initializationServices.GetFileSystem(); + + var assemblies = _initializationServices.GetAssemblyResolver() + .GetAssemblyPaths(fileSystem.GetWorkingDirectory(_scriptName)) + .Where(assembly => ShouldLoadAssembly(fileSystem, _initializationServices.GetAssemblyUtility(), assembly)); var aggregateCatalog = new AggregateCatalog(); - bool assemblyLoadFailures = false; + var assemblyLoadFailures = false; - foreach (var assembly in assemblies) + foreach (var assemblyPath in assemblies) { try { - var catalog = new AssemblyCatalog(assembly); - catalog.Parts.ToList(); //force the Parts to be read + var catalog = new AssemblyCatalog(assemblyPath); + // force the parts to be queried to catch any errors that would otherwise show up later + catalog.Parts.ToList(); aggregateCatalog.Catalogs.Add(catalog); } - catch(Exception ex) + catch (ReflectionTypeLoadException typeLoadEx) + { + assemblyLoadFailures = true; + if (typeLoadEx.LoaderExceptions != null && typeLoadEx.LoaderExceptions.Any()) + { + foreach (var ex in typeLoadEx.LoaderExceptions.GroupBy(x => x.Message)) + { + _log.DebugFormat( + "Failure loading assembly: {0}. Exception: {1}", assemblyPath, ex.First().Message); + } + } + } + catch (Exception ex) { assemblyLoadFailures = true; - Logger.DebugFormat("Failure loading assembly: {0}. Exception: {1}", assembly, ex.Message); + _log.DebugFormat("Failure loading assembly: {0}. Exception: {1}", assemblyPath, ex.Message); } } if (assemblyLoadFailures) { - if (_scriptName == null || _scriptName.Length == 0) - Logger.Warn("Some assemblies failed to load. Launch with '-repl -loglevel debug' to see the details"); - else - Logger.Warn("Some assemblies failed to load. Launch with '-loglevel debug' to see the details"); + _log.Warn(string.IsNullOrEmpty(_scriptName) + ? "Some assemblies failed to load. Launch with '-repl -loglevel debug' to see the details" + : "Some assemblies failed to load. Launch with '-loglevel debug' to see the details"); } builder.RegisterComposablePartCatalog(aggregateCatalog); } @@ -97,29 +159,27 @@ protected override IContainer CreateContainer() return builder.Build(); } - private void RegisterLineProcessors(ContainerBuilder builder) + // HACK: Filter out assemblies in the GAC by checking if full path is specified. + private static bool ShouldLoadAssembly(IFileSystem fileSystem, IAssemblyUtility assemblyUtility, string assembly) { - var loadProcessorType = _lineProcessors - .FirstOrDefault(x => typeof(ILoadLineProcessor).IsAssignableFrom(x)) - ?? typeof(LoadLineProcessor); - - var usingProcessorType = _lineProcessors - .FirstOrDefault(x => typeof(IUsingLineProcessor).IsAssignableFrom(x)) - ?? typeof(UsingLineProcessor); - - var referenceProcessorType = _lineProcessors - .FirstOrDefault(x => typeof(IReferenceLineProcessor).IsAssignableFrom(x)) - ?? typeof(ReferenceLineProcessor); + return fileSystem.IsPathRooted(assembly) && assemblyUtility.IsManagedAssembly(assembly); + } - var lineProcessors = new[] { loadProcessorType, usingProcessorType, referenceProcessorType }.Union(_lineProcessors); + private static void RegisterReplCommands(ContainerBuilder builder) + { + var replCommands = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => !x.IsDynamic) + .SelectMany(x => x.GetExportedTypes()) + .Where(x => typeof(IReplCommand).IsAssignableFrom(x) && x.IsClass && !x.IsAbstract) + .ToArray(); - builder.RegisterTypes(lineProcessors.ToArray()).As(); + builder.RegisterTypes(replCommands).As(); } public ScriptServices GetScriptServices() { - this.Logger.Debug("Resolving ScriptServices"); + _log.Debug("Resolving ScriptServices"); return Container.Resolve(); } } -} +} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ScriptConsole.cs b/src/ScriptCs.Hosting/ScriptConsole.cs index 931b9dd7..251e720e 100644 --- a/src/ScriptCs.Hosting/ScriptConsole.cs +++ b/src/ScriptCs.Hosting/ScriptConsole.cs @@ -1,13 +1,16 @@ using System; using ScriptCs.Contracts; +using Mono.Terminal; -namespace ScriptCs +namespace ScriptCs.Hosting { public class ScriptConsole : IConsole { + private LineEditor _editor; + public ScriptConsole() { - Console.CancelKeyPress += HandleCancelKeyPress; + _editor = new LineEditor ("scriptcs"); } public void Write(string value) @@ -25,9 +28,9 @@ public void WriteLine(string value) Console.WriteLine(value); } - public string ReadLine() + public string ReadLine(string prompt) { - return Console.ReadLine(); + return _editor.Edit (prompt, ""); } public void Clear() @@ -38,7 +41,7 @@ public void Clear() public void Exit() { ResetColor(); - Console.CancelKeyPress -= HandleCancelKeyPress; + Environment.Exit(0); } public void ResetColor() @@ -46,15 +49,12 @@ public void ResetColor() Console.ResetColor(); } - private void HandleCancelKeyPress(object sender, ConsoleCancelEventArgs e) - { - ResetColor(); - } - public ConsoleColor ForegroundColor { - get { return Console.ForegroundColor; } - set { Console.ForegroundColor = value; } + get => Console.ForegroundColor; + set => Console.ForegroundColor = value; } + + public int Width => Console.BufferWidth; } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ScriptConsoleAppender.cs b/src/ScriptCs.Hosting/ScriptConsoleAppender.cs deleted file mode 100644 index b70f4e7a..00000000 --- a/src/ScriptCs.Hosting/ScriptConsoleAppender.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text; -using log4net.Appender; -using log4net.Core; -using ScriptCs.Contracts; - -namespace ScriptCs -{ - public class ScriptConsoleAppender : AppenderSkeleton - { - private readonly IConsole _console; - - public ScriptConsoleAppender(IConsole console) - { - _console = console; - } - - protected override void Append(LoggingEvent loggingEvent) - { - Guard.AgainstNullArgument("loggingEvent", loggingEvent); - - var txt = new StringBuilder(); - txt.Append(loggingEvent.Level); - txt.Append(": "); - txt.Append(loggingEvent.RenderedMessage); - _console.WriteLine(txt.ToString()); - } - } -} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ScriptCs.Hosting.csproj b/src/ScriptCs.Hosting/ScriptCs.Hosting.csproj index 4e65f833..3a164047 100644 --- a/src/ScriptCs.Hosting/ScriptCs.Hosting.csproj +++ b/src/ScriptCs.Hosting/ScriptCs.Hosting.csproj @@ -1,108 +1,31 @@ - - - + - {9AEF2D95-87FB-4829-B384-34BFE076D531} - Library - ScriptCs.Hosting - ScriptCs.Hosting - ..\..\ + 1.0.0 + net461 + ScriptCs.Hosting + Glenn Block, Filip Wojcieszyn, Justin Rusbatch + https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md + http://scriptcs.net + http://www.gravatar.com/avatar/5c754f646971d8bc800b9d4057931938.png?s=120 + ScriptCs.Hosting + ScriptCs.Hosting provides common services necessary for hosting scriptcs in your application. + roslyn csx script scriptcs - - False - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.dll - - - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.Configuration.dll - - - False - ..\..\packages\Autofac.Mef.3.0.1\lib\net40\Autofac.Integration.Mef.dll - - - False - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - ..\..\packages\Common.Logging.Log4Net.2.0.1\lib\net20\Common.Logging.Log4Net.dll - - - ..\..\packages\log4net.1.2.10\lib\2.0\log4net.dll - - - ..\..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll - - - False - ..\..\packages\Nuget.Core.2.7.0\lib\net40-Client\NuGet.Core.dll - - - - - - - - - + - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - Guard.cs - - - - - - - - - - - - - - - - - - - - - - + + + + + - - + + - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - {e590e710-e159-48e6-a3e6-1a83d3fe732c} - ScriptCs.Core - - - {e79ec231-e27d-4057-91c9-2d001a3a8c3b} - ScriptCs.Engine.Roslyn - + - - - \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ScriptServicesBuilder.cs b/src/ScriptCs.Hosting/ScriptServicesBuilder.cs index 9e48cf84..cd95d4f2 100644 --- a/src/ScriptCs.Hosting/ScriptServicesBuilder.cs +++ b/src/ScriptCs.Hosting/ScriptServicesBuilder.cs @@ -1,56 +1,72 @@ using System; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Common.Logging; +using System.Collections.Generic; using ScriptCs.Contracts; -using ScriptCs.Engine.Roslyn; -using ScriptCs.Hosting; -using log4net.Core; -using LogLevel = ScriptCs.Contracts.LogLevel; - -namespace ScriptCs +namespace ScriptCs.Hosting { public class ScriptServicesBuilder : ServiceOverrides, IScriptServicesBuilder { - private readonly IInitializationServices _initializationServices; + private readonly ILogProvider _logProvider; + private IRuntimeServices _runtimeServices; - private readonly IConsole _console; - private bool _repl = false; - private bool _cache = false; + private bool _repl; + private bool _cache; + private bool _debug; private string _scriptName; private LogLevel _logLevel; private Type _scriptExecutorType; + private Type _replType; private Type _scriptEngineType; - private ILog _logger; + private bool? _loadScriptPacks; - public ScriptServicesBuilder(IConsole console, ILog logger, IRuntimeServices runtimeServices = null) + public ScriptServicesBuilder( + IConsole console, + ILogProvider logProvider, + IRuntimeServices runtimeServices = null, + IInitializationServices initializationServices = null) { - _initializationServices = new InitializationServices(logger); + InitializationServices = initializationServices ?? new InitializationServices(logProvider); _runtimeServices = runtimeServices; - _console = console; - _logger = logger; + ConsoleInstance = console; + _logProvider = logProvider; } public ScriptServices Build() { var defaultExecutorType = typeof(ScriptExecutor); - Type defaultEngineType = _cache ? typeof(RoslynScriptPersistentEngine) : typeof(RoslynScriptInMemoryEngine); + _scriptExecutorType = Overrides.ContainsKey(typeof(IScriptExecutor)) + ? (Type)Overrides[typeof(IScriptExecutor)] + : defaultExecutorType; - defaultEngineType = _repl ? typeof(RoslynScriptEngine) : defaultEngineType; + var defaultReplType = typeof(Repl); + _replType = Overrides.ContainsKey(typeof(IRepl)) ? (Type)Overrides[typeof(IRepl)] : defaultReplType; - _scriptExecutorType = Overrides.ContainsKey(typeof(IScriptExecutor)) ? (Type)Overrides[typeof(IScriptExecutor)] : defaultExecutorType; - _scriptEngineType = Overrides.ContainsKey(typeof(IScriptEngine)) ? (Type)Overrides[typeof(IScriptEngine)] : defaultEngineType; + _scriptEngineType = (Type)Overrides[typeof(IScriptEngine)]; - var initDirectoryCatalog = _scriptName != null || _repl; + bool initDirectoryCatalog; + + if (_loadScriptPacks.HasValue) + { + initDirectoryCatalog = _loadScriptPacks.Value; + } + else + { + initDirectoryCatalog = _scriptName != null || _repl; + } if (_runtimeServices == null) { - _runtimeServices = new RuntimeServices(_logger, Overrides, LineProcessors, _console, - _scriptEngineType, _scriptExecutorType, - initDirectoryCatalog, - _initializationServices, _scriptName); + _runtimeServices = new RuntimeServices( + _logProvider, + Overrides, + ConsoleInstance, + _scriptEngineType, + _scriptExecutorType, + _replType, + initDirectoryCatalog, + InitializationServices, + _scriptName); } return _runtimeServices.GetScriptServices(); @@ -58,9 +74,21 @@ public ScriptServices Build() public IScriptServicesBuilder LoadModules(string extension, params string[] moduleNames) { - var config = new ModuleConfiguration(_cache, _scriptName, _repl, _logLevel, Overrides); - var loader = _initializationServices.GetModuleLoader(); - loader.Load(config, _initializationServices.GetFileSystem().ModulesFolder, extension, moduleNames); + moduleNames = moduleNames.Union(new[] { "roslyn" }).ToArray(); + + var config = new ModuleConfiguration(_cache, _scriptName, _repl, _logLevel, _debug, Overrides); + var loader = InitializationServices.GetModuleLoader(); + + var fs = InitializationServices.GetFileSystem(); + + var folders = new[] { fs.GlobalFolder }; + loader.Load(config, folders, InitializationServices.GetFileSystem().HostBin, extension, moduleNames); + return this; + } + + public IScriptServicesBuilder LoadScriptPacks(bool load = true) + { + _loadScriptPacks = load; return this; } @@ -87,5 +115,29 @@ public IScriptServicesBuilder LogLevel(LogLevel level) _logLevel = level; return this; } + + public IScriptServicesBuilder Debug(bool debug = true) + { + _debug = debug; + return this; + } + + public IScriptServicesBuilder SetOverride(TImpl value) where TImpl : TContract + { + Overrides[typeof(TContract)] = value; + return this; + } + + public IScriptServicesBuilder SetOverride() where TImpl : TContract + { + Overrides[typeof(TContract)] = typeof(TImpl); + return this; + } + + public IInitializationServices InitializationServices { get; private set; } + + public IConsole ConsoleInstance { get; private set; } + + internal IRuntimeServices RuntimeServices => _runtimeServices; } } \ No newline at end of file diff --git a/src/ScriptCs.Hosting/ScriptServicesRegistration.cs b/src/ScriptCs.Hosting/ScriptServicesRegistration.cs index e1cace3e..33d03631 100644 --- a/src/ScriptCs.Hosting/ScriptServicesRegistration.cs +++ b/src/ScriptCs.Hosting/ScriptServicesRegistration.cs @@ -1,22 +1,26 @@ using System; using System.Collections.Generic; - +using System.Linq; +using ScriptCs.Contracts; using Autofac; -using Common.Logging; - -namespace ScriptCs +namespace ScriptCs.Hosting { public abstract class ScriptServicesRegistration { - private readonly IDictionary _overrides = null; + private readonly ILogProvider _logProvider; + private readonly ILog _log; + private readonly IDictionary _overrides; - protected ILog Logger { get; private set; } + public ILogProvider LogProvider => _logProvider; - public ScriptServicesRegistration(ILog logger, IDictionary overrides) + protected ScriptServicesRegistration(ILogProvider logProvider, IDictionary overrides) { + Guard.AgainstNullArgument("logProvider", logProvider); + _overrides = overrides ?? new Dictionary(); - Logger = logger; + _logProvider = logProvider; + _log = _logProvider.ForCurrentType(); } protected void RegisterOverrideOrDefault(ContainerBuilder builder, Action registrationAction) @@ -26,7 +30,7 @@ protected void RegisterOverrideOrDefault(ContainerBuilder builder, Action(ContainerBuilder builder, Action ?? Enumerable.Empty()).ToArray(); - return _container; - } + var loadProcessorType = processorList + .FirstOrDefault(x => typeof(ILoadLineProcessor).IsAssignableFrom(x)) + ?? typeof(LoadLineProcessor); + + var usingProcessorType = processorList + .FirstOrDefault(x => typeof(IUsingLineProcessor).IsAssignableFrom(x)) + ?? typeof(UsingLineProcessor); + + var referenceProcessorType = processorList + .FirstOrDefault(x => typeof(IReferenceLineProcessor).IsAssignableFrom(x)) + ?? typeof(ReferenceLineProcessor); + + var shebangProcessorType = processorList + .FirstOrDefault(x => typeof(IShebangLineProcessor).IsAssignableFrom(x)) + ?? typeof(ShebangLineProcessor); + + var processorArray = new[] { loadProcessorType, usingProcessorType, referenceProcessorType, shebangProcessorType } + .Union(processorList).ToArray(); + + builder.RegisterTypes(processorArray).As(); } + private IContainer _container; + + public IContainer Container => _container ?? (_container = CreateContainer()); + + protected IDictionary Overrides => _overrides; + protected abstract IContainer CreateContainer(); } } diff --git a/src/ScriptCs.Hosting/ServiceOverrides.cs b/src/ScriptCs.Hosting/ServiceOverrides.cs index c996293f..c303189c 100644 --- a/src/ScriptCs.Hosting/ServiceOverrides.cs +++ b/src/ScriptCs.Hosting/ServiceOverrides.cs @@ -1,33 +1,33 @@ using System; using System.Collections.Generic; - +using System.Linq; using ScriptCs.Contracts; -namespace ScriptCs +namespace ScriptCs.Hosting { public abstract class ServiceOverrides : IServiceOverrides where TConfig : class, IServiceOverrides { - protected readonly IList LineProcessors = new List(); - - public readonly IDictionary Overrides = new Dictionary(); + public IDictionary Overrides { get; private set; } private readonly TConfig _this; protected ServiceOverrides() { _this = this as TConfig; + Overrides = new Dictionary(); } - public TConfig ScriptHostFactory() where T : IScriptHostFactory + protected ServiceOverrides(IDictionary overrides) + : this() { - Overrides[typeof (IScriptHostFactory)] = typeof (T); - return _this; + Overrides = overrides; } - protected ServiceOverrides(IDictionary overrides) + public TConfig ScriptHostFactory() where T : IScriptHostFactory { - Overrides = overrides; + Overrides[typeof(IScriptHostFactory)] = typeof(T); + return _this; } public TConfig ScriptExecutor() where T : IScriptExecutor @@ -36,6 +36,12 @@ public TConfig ScriptExecutor() where T : IScriptExecutor return _this; } + public TConfig Repl() where T : IRepl + { + Overrides[typeof(IRepl)] = typeof(T); + return _this; + } + public TConfig ScriptEngine() where T : IScriptEngine { Overrides[typeof(IScriptEngine)] = typeof(T); @@ -116,8 +122,26 @@ public TConfig Console() where T : IConsole public TConfig LineProcessor() where T : ILineProcessor { - LineProcessors.Add(typeof(T)); + var key = typeof(ILineProcessor); + object value; + if (!Overrides.TryGetValue(key, out value) || value == null) + { + value = Enumerable.Empty(); + } + + IEnumerable processors; + try + { + processors = (IEnumerable)value; + } + catch (InvalidCastException ex) + { + throw new InvalidOperationException("The line processors override has an invalid type.", ex); + } + + Overrides[typeof(ILineProcessor)] = new[] { typeof(T) }.Concat(processors).ToList(); + return _this; } } -} \ No newline at end of file +} diff --git a/src/ScriptCs.Hosting/TypeResolver.cs b/src/ScriptCs.Hosting/TypeResolver.cs new file mode 100644 index 00000000..36f7c35e --- /dev/null +++ b/src/ScriptCs.Hosting/TypeResolver.cs @@ -0,0 +1,9 @@ +using System; + +namespace ScriptCs.Hosting +{ + public class TypeResolver : ITypeResolver + { + public Type ResolveType(string type) => Type.GetType(type); + } +} \ No newline at end of file diff --git a/src/ScriptCs.Hosting/VisualStudioSolution.cs b/src/ScriptCs.Hosting/VisualStudioSolution.cs new file mode 100644 index 00000000..855f47c6 --- /dev/null +++ b/src/ScriptCs.Hosting/VisualStudioSolution.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ScriptCs.Contracts; + +namespace ScriptCs.Hosting +{ + public class VisualStudioSolution : IVisualStudioSolution + { + internal StringBuilder Header { get; set; } + internal StringBuilder Projects { get; set; } + internal StringBuilder Global { get; set; } + + public VisualStudioSolution() + { + Header = new StringBuilder(); + Projects = new StringBuilder(); + Global = new StringBuilder(); + AddHeader(); + } + + public void AddHeader() + { + Header.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); + Header.AppendLine("# Visual Studio 2013"); + Header.AppendLine("VisualStudioVersion = 12.0.30501.0"); + Header.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); + } + + public void AddScriptcsProject(string scriptcsPath, string workingPath, string args, bool attach, Guid projectGuid) + { + Projects.AppendFormat(@"Project(""{{911E67C6-3D85-4FCE-B560-20A9C3E3FF48}}"") = ""scriptcs"", ""{0}"", ""{{{1}}}""{2}", scriptcsPath, projectGuid, Environment.NewLine); + Projects.AppendLine("\tProjectSection(DebuggerProjectSystem) = preProject"); + Projects.AppendLine("\t\tPortSupplier = 00000000-0000-0000-0000-000000000000"); + Projects.AppendFormat("\t\tExecutable = {0}{1}", scriptcsPath, Environment.NewLine); + Projects.AppendLine("\t\tRemoteMachine = localhost"); + Projects.AppendFormat("\t\tStartingDirectory = {0}{1}", workingPath, Environment.NewLine); + Projects.AppendFormat("\t\tArguments = {0}{1}", args, Environment.NewLine); + Projects.AppendLine("\t\tEnvironment = Default"); + Projects.AppendLine("\t\tLaunchingEngine = 00000000-0000-0000-0000-000000000000"); + Projects.AppendLine("\t\tUseLegacyDebugEngines = No"); + Projects.AppendLine("\t\tLaunchSQLEngine = No"); + Projects.AppendFormat("\t\tAttachLaunchAction = {0}{1}", attach == true ? "Yes" : "No", Environment.NewLine); + Projects.AppendLine("\tEndProjectSection"); + Projects.AppendLine("EndProject"); + } + + public void AddProject(string path, string name, Guid guid, string[] files) + { + Projects.AppendFormat(@"Project(""{{2150E333-8FDC-42A3-9474-1A3956D46DE8}}"") = ""{0}"", ""{0}"", ""{{{1}}}""{2}", name, guid, Environment.NewLine); + Projects.AppendLine("\tProjectSection(SolutionItems) = preProject"); + foreach (var file in files) + { + if (path == null) + { + Projects.AppendFormat("\t\t{0} = {0}{1}", file, Environment.NewLine); + } + else + { + Projects.AppendFormat("\t\t{0}\\{1} = {0}\\{1}{2}", path, file, Environment.NewLine); + } + } + Projects.AppendLine("\tEndProjectSection"); + Projects.AppendLine("EndProject"); + } + + public void AddGlobalHeader(Guid projectGuid) + { + Global.AppendLine("Global"); + Global.AppendLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution"); + Global.AppendLine("\t\tDebug|Any CPU = Debug|Any CPU"); + Global.AppendLine("\tEndGlobalSection"); + Global.AppendLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution"); + Global.AppendFormat("\t\t{{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU{1}", projectGuid, Environment.NewLine); + Global.AppendLine("\tEndGlobalSection"); + Global.AppendLine("\tGlobalSection(SolutionProperties) = preSolution"); + Global.AppendLine("\t\tHideSolutionNode = FALSE"); + Global.AppendLine("\tEndGlobalSection"); + } + + public void AddGlobalNestedProjects(IList nestedItems) + { + if (nestedItems.Any()) + { + Global.AppendLine("\tGlobalSection(NestedProjects) = preSolution"); + foreach (var item in nestedItems) + { + Global.AppendFormat("\t\t{{{0}}} = {{{1}}}{2}", item.Project, item.Parent, Environment.NewLine); + } + Global.AppendLine("\tEndGlobalSection"); + } + } + + public void AddGlobal(Guid projectGuid, IList nestedItems) + { + AddGlobalHeader(projectGuid); + AddGlobalNestedProjects(nestedItems); + Global.AppendLine("EndGlobal"); + } + + public override string ToString() + { + var solutionBuilder = new StringBuilder(); + solutionBuilder.Append(Header); + solutionBuilder.Append(Projects); + solutionBuilder.Append(Global); + return solutionBuilder.ToString(); + } + } +} diff --git a/src/ScriptCs.Hosting/VisualStudioSolutionWriter.cs b/src/ScriptCs.Hosting/VisualStudioSolutionWriter.cs new file mode 100644 index 00000000..244f46e4 --- /dev/null +++ b/src/ScriptCs.Hosting/VisualStudioSolutionWriter.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ScriptCs.Contracts; + +namespace ScriptCs.Hosting +{ + public class VisualStudioSolutionWriter : IVisualStudioSolutionWriter + { + internal DirectoryInfo Root { get; set; } + private Guid _nullGuid = new Guid(); + + public string WriteSolution(IFileSystem fs, string script, IVisualStudioSolution solution, IList nestedItems = null) + { + if (nestedItems == null) + { + nestedItems = new List(); + } + + var launcher = Path.Combine(fs.TempPath, "launcher-" + Guid.NewGuid().ToString() + ".sln"); + + if (fs.FileExists(launcher)) + { + fs.FileDelete(launcher); + } + + var currentDir = fs.CurrentDirectory; + var scriptcsPath = Path.Combine(fs.HostBin, "scriptcs.exe"); + var scriptcsArgs = string.Format("{0} -debug -loglevel info", script); + Root = new DirectoryInfo { Name = "Solution Items", FullPath = currentDir}; + var projectGuid = Guid.NewGuid(); + + solution.AddScriptcsProject(scriptcsPath, currentDir, scriptcsArgs, false, projectGuid); + GetDirectoryInfo(fs, currentDir, Root); + AddDirectoryProject(solution, fs, Root, _nullGuid, nestedItems); + solution.AddGlobal(projectGuid, nestedItems); + fs.WriteToFile(launcher, solution.ToString()); + return launcher; + } + + private void AddDirectoryProject(IVisualStudioSolution solution, IFileSystem fs, DirectoryInfo currentDirectory, Guid parent, IList nestedItems) + { + solution.AddProject(currentDirectory.FullPath, currentDirectory.Name, currentDirectory.Guid, currentDirectory.Files.ToArray()); + foreach (DirectoryInfo dir in currentDirectory.Directories.Values) + { + AddDirectoryProject(solution, fs, dir, currentDirectory.Guid, nestedItems); + } + if (parent != _nullGuid) + { + nestedItems.Add(new ProjectItem(currentDirectory.Guid, parent)); + } + } + + private void GetDirectoryInfo(IFileSystem fs, string currentDir, DirectoryInfo root) + { + IEnumerable files; + var packagesFolder = Path.Combine(currentDir, fs.PackagesFolder); + files = fs.EnumerateFilesAndDirectories(currentDir, "*.csx").Where( + f => f.StartsWith(packagesFolder).Equals(false)); + + var skip = currentDir.Length; + + foreach (var file in files) + { + var pruned = file.Substring(skip + 1); + var segments = pruned.Split(Path.DirectorySeparatorChar); + if (segments.Length == 1) + { + root.Files.Add(segments[0]); + } + else + { + var currentDirectory = root; + var path = ""; + for (int i = 0; i < segments.Length - 1; i++) + { + var segment = segments[i]; + path = path + segment + @"\"; + if (!currentDirectory.Directories.ContainsKey(segment)) + { + var newDirectory = new DirectoryInfo { Name = segment, Path=path, FullPath = Path.GetDirectoryName(file)}; + currentDirectory.Directories[segment] = newDirectory; + currentDirectory = newDirectory; + } + else + { + currentDirectory = currentDirectory.Directories[segment]; + } + } + currentDirectory.Files.Add(segments[segments.Length - 1]); + } + } + } + } +} diff --git a/src/ScriptCs.Hosting/packages.config b/src/ScriptCs.Hosting/packages.config deleted file mode 100644 index 007ff07b..00000000 --- a/src/ScriptCs.Hosting/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/ScriptCs/Application.cs b/src/ScriptCs/Application.cs new file mode 100644 index 00000000..7b80c876 --- /dev/null +++ b/src/ScriptCs/Application.cs @@ -0,0 +1,15 @@ +namespace ScriptCs +{ + using ScriptCs.Command; + + internal static class Application + { + public static int Run(Config config, string[] scriptArgs) + { + var scriptServicesBuilder = ScriptServicesBuilderFactory.Create(config, scriptArgs); + var factory = new CommandFactory(scriptServicesBuilder); + var command = factory.CreateCommand(config, scriptArgs); + return (int)command.Execute(); + } + } +} diff --git a/src/ScriptCs/Argument/ArgumentHandler.cs b/src/ScriptCs/Argument/ArgumentHandler.cs deleted file mode 100644 index a9ee976b..00000000 --- a/src/ScriptCs/Argument/ArgumentHandler.cs +++ /dev/null @@ -1,134 +0,0 @@ -using PowerArgs; -using ScriptCs.Contracts; -using ServiceStack.Text; -using System; -using System.Linq; -using System.Reflection; - -namespace ScriptCs.Argument -{ - public class ArgumentHandler : IArgumentHandler - { - private readonly IArgumentParser _argumentParser; - private readonly IConfigFileParser _configFileParser; - private readonly IFileSystem _fileSystem; - - public ArgumentHandler(IArgumentParser argumentParser, IConfigFileParser configFileParser, IFileSystem fileSystem) - { - Guard.AgainstNullArgument("argumentParser", argumentParser); - Guard.AgainstNullArgument("configFileParser", configFileParser); - Guard.AgainstNullArgument("fileSystem", fileSystem); - - _fileSystem = fileSystem; - _argumentParser = argumentParser; - _configFileParser = configFileParser; - } - - public ArgumentParseResult Parse(string[] args) - { - var sr = SplitScriptArgs(args); - - var commandArgs = _argumentParser.Parse(sr.CommandArguments); - var configArgs = _configFileParser.Parse(GetFileContent(commandArgs != null ? commandArgs.Config : null)); - var finalArguments = ReconcileArguments(configArgs, commandArgs, sr); - - return new ArgumentParseResult(args, finalArguments, sr.ScriptArguments); - } - - private string GetFileContent(string fileName) - { - string filePath = _fileSystem.CurrentDirectory + '\\' + fileName; - if (_fileSystem.FileExists(filePath)) - { - return _fileSystem.ReadFile(filePath); - } - - return null; - } - - public static SplitResult SplitScriptArgs(string[] args) - { - Guard.AgainstNullArgument("args", args); - - var result = new SplitResult() { CommandArguments = args }; - - // Split the arguments list on "--". - // The arguments before the "--" (or all arguments if there is no "--") are - // for ScriptCs.exe, and the arguments after that are for the csx script. - int separatorLocation = Array.IndexOf(args, "--"); - int scriptArgsCount = separatorLocation == -1 ? 0 : args.Length - separatorLocation - 1; - result.ScriptArguments = new string[scriptArgsCount]; - Array.Copy(args, separatorLocation + 1, result.ScriptArguments, 0, scriptArgsCount); - - if (separatorLocation != -1) - result.CommandArguments = args.Take(separatorLocation).ToArray(); - - return result; - } - - private static ScriptCsArgs ReconcileArguments(ScriptCsArgs configArgs, ScriptCsArgs commandArgs, SplitResult splitResult) - { - if (configArgs == null) - return commandArgs; - - if (commandArgs == null) - return configArgs; - - foreach (var property in typeof(ScriptCsArgs).GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - var configValue = property.GetValue(configArgs); - var commandValue = property.GetValue(commandArgs); - var defaultValue = GetPropertyDefaultValue(property); - - if (!object.Equals(configValue, commandValue)) - { - if (!object.Equals(commandValue, defaultValue)) - { - property.SetValue(configArgs, commandValue); - } - else - { - if (IsCommandLinePresent(splitResult.CommandArguments, property)) - property.SetValue(configArgs, commandValue); - } - } - } - - return configArgs; - } - - private static bool IsCommandLinePresent(string[] args, PropertyInfo property) - { - bool attributeFound = false; - - var attribute = property.GetCustomAttribute(); - - if (attribute != null) - attributeFound = args.Any(a => a.Contains((attribute as ArgShortcut).Shortcut)); - - var result = args.Any(a => a.Contains(property.Name)) || attributeFound; - return result; - } - - private static object GetPropertyDefaultValue(PropertyInfo property) - { - var defaultAttribute = property.GetCustomAttribute(); - - return defaultAttribute != null - ? ((PowerArgs.DefaultValueAttribute)defaultAttribute).Value - : property.PropertyType.GetDefaultValue(); - } - - public class SplitResult - { - public SplitResult() - { - CommandArguments = new string[0]; - ScriptArguments = new string[0]; - } - - public string[] CommandArguments { get; set; } - public string[] ScriptArguments { get; set; } - } - } -} \ No newline at end of file diff --git a/src/ScriptCs/Argument/ArgumentParseResult.cs b/src/ScriptCs/Argument/ArgumentParseResult.cs deleted file mode 100644 index fea10dc2..00000000 --- a/src/ScriptCs/Argument/ArgumentParseResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace ScriptCs.Argument -{ - public class ArgumentParseResult - { - public ArgumentParseResult(string[] arguments, ScriptCsArgs commandArguments, string[] scriptArguments) - { - Arguments = arguments; - CommandArguments = commandArguments; - ScriptArguments = scriptArguments; - } - - public string[] Arguments { get; private set; } - public ScriptCsArgs CommandArguments { get; private set; } - public string[] ScriptArguments { get; private set; } - } -} \ No newline at end of file diff --git a/src/ScriptCs/Argument/ArgumentParser.cs b/src/ScriptCs/Argument/ArgumentParser.cs deleted file mode 100644 index c03cc71b..00000000 --- a/src/ScriptCs/Argument/ArgumentParser.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Linq; -using PowerArgs; -using ScriptCs.Contracts; - -namespace ScriptCs.Argument -{ - public class ArgumentParser : IArgumentParser - { - private readonly IConsole _console; - - public ArgumentParser(IConsole console) - { - _console = console; - } - - public ScriptCsArgs Parse(string[] args) - { - //no args initialized REPL - if (args == null || args.Length <= 0) - return new ScriptCsArgs { Repl = true }; - - ScriptCsArgs commandArgs = null; - const string unexpectedArgumentMessage = "Unexpected Argument: "; - - try - { - commandArgs = Args.Parse(args); - - //if there is only 1 arg and it is a loglevel, it's also REPL - if (args.Length == 2 && args.Any(x => x.ToLowerInvariant() == "-loglevel" || x.ToLowerInvariant() == "-log")) - { - commandArgs.Repl = true; - } - } - catch(ArgException ex) - { - if(ex.Message.StartsWith(unexpectedArgumentMessage)) - { - var token = ex.Message.Substring(unexpectedArgumentMessage.Length); - _console.WriteLine(string.Format("Parameter \"{0}\" is not supported!", token)); - } - else - { - _console.WriteLine(ex.Message); - } - } - - return commandArgs; - } - } -} \ No newline at end of file diff --git a/src/ScriptCs/Argument/ConfigFileParser.cs b/src/ScriptCs/Argument/ConfigFileParser.cs deleted file mode 100644 index 98ef4c8e..00000000 --- a/src/ScriptCs/Argument/ConfigFileParser.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using PowerArgs; -using ScriptCs.Contracts; -using ServiceStack.Text; - -namespace ScriptCs.Argument -{ - public class ConfigFileParser : IConfigFileParser - { - private readonly IConsole _console; - - public ConfigFileParser(IConsole console) - { - _console = console; - } - - public ScriptCsArgs Parse(string content) - { - if (!string.IsNullOrWhiteSpace(content)) - { - var fromJson = ParseJson(content); - - if (fromJson != null) - { - var arguments = new ScriptCsArgs(); - var configFileValues = new Dictionary(fromJson, StringComparer.InvariantCultureIgnoreCase); - - foreach (var property in typeof(ScriptCsArgs).GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - string key = ""; - - var attributes = property.GetCustomAttributes(false) - .ToDictionary(a => a.GetType().Name, a => a); - - if (attributes.ContainsKey(typeof(ArgIgnoreAttribute).Name)) - continue; - - if (attributes.ContainsKey(typeof(ArgShortcut).Name)) - { - var attribute = (attributes[typeof(ArgShortcut).Name] as ArgShortcut); - - if (attribute != null) - { - if (configFileValues.ContainsKey(property.Name)) - { - key = property.Name; - } - - if (string.IsNullOrEmpty(key) && configFileValues.ContainsKey(attribute.Shortcut)) - { - key = attribute.Shortcut; - } - } - } - - if (!string.IsNullOrEmpty(key)) - { - string value = configFileValues[key]; - var converter = TypeDescriptor.GetConverter(property.PropertyType); - var result = converter.ConvertFrom(value); - - property.SetValue(arguments, result); - } - } - - return arguments; - } - } - - return null; - } - - private Dictionary ParseJson(string content) - { - Dictionary dict = null; - - try - { - dict = JsonSerializer.DeserializeFromString>(content); - } - catch - { - _console.WriteLine("Error parsing configuration file."); - } - - return dict; - } - } -} \ No newline at end of file diff --git a/src/ScriptCs/Argument/IArgumentHandler.cs b/src/ScriptCs/Argument/IArgumentHandler.cs deleted file mode 100644 index 9da0eed8..00000000 --- a/src/ScriptCs/Argument/IArgumentHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ScriptCs.Argument -{ - public interface IArgumentHandler - { - ArgumentParseResult Parse(string[] args); - } -} \ No newline at end of file diff --git a/src/ScriptCs/Argument/IArgumentParser.cs b/src/ScriptCs/Argument/IArgumentParser.cs deleted file mode 100644 index 8b29e8ff..00000000 --- a/src/ScriptCs/Argument/IArgumentParser.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ScriptCs.Argument -{ - public interface IArgumentParser - { - ScriptCsArgs Parse(string[] args); - } -} \ No newline at end of file diff --git a/src/ScriptCs/Argument/IConfigFileParser.cs b/src/ScriptCs/Argument/IConfigFileParser.cs deleted file mode 100644 index 86bc1fe9..00000000 --- a/src/ScriptCs/Argument/IConfigFileParser.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ScriptCs.Argument -{ - public interface IConfigFileParser - { - ScriptCsArgs Parse(string content); - } -} \ No newline at end of file diff --git a/src/ScriptCs/Command/CleanCommand.cs b/src/ScriptCs/Command/CleanCommand.cs index 1f570c5b..f543c595 100644 --- a/src/ScriptCs/Command/CleanCommand.cs +++ b/src/ScriptCs/Command/CleanCommand.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using Common.Logging; - using ScriptCs.Contracts; namespace ScriptCs.Command @@ -9,16 +7,19 @@ namespace ScriptCs.Command internal class CleanCommand : ICleanCommand { private readonly string _scriptName; - private readonly IFileSystem _fileSystem; - private readonly ILog _logger; - public CleanCommand(string scriptName, IFileSystem fileSystem, ILog logger) + public CleanCommand(string scriptName, IFileSystem fileSystem, ILogProvider logProvider) { + Guard.AgainstNullArgumentProperty("fileSystem", "PackagesFolder", fileSystem.PackagesFolder); + Guard.AgainstNullArgumentProperty("fileSystem", "DllCacheFolder", fileSystem.DllCacheFolder); + + Guard.AgainstNullArgument("logProvider", logProvider); + _scriptName = scriptName; _fileSystem = fileSystem; - _logger = logger; + _logger = logProvider.ForCurrentType(); } public CommandResult Execute() @@ -28,9 +29,12 @@ public CommandResult Execute() var workingDirectory = _fileSystem.GetWorkingDirectory(_scriptName); _logger.TraceFormat("Working directory: {0}", workingDirectory); - var packageFolder = Path.Combine(workingDirectory, Constants.PackagesFolder); + var packageFolder = Path.Combine(workingDirectory, _fileSystem.PackagesFolder); _logger.TraceFormat("Packages folder: {0}", packageFolder); + var cacheFolder = Path.Combine(workingDirectory, _fileSystem.DllCacheFolder); + _logger.TraceFormat("Cache folder: {0}", cacheFolder); + try { if (_fileSystem.DirectoryExists(packageFolder)) @@ -39,14 +43,20 @@ public CommandResult Execute() _fileSystem.DeleteDirectory(packageFolder); } - _logger.Info("Clean completed successfully."); + if (_fileSystem.DirectoryExists(cacheFolder)) + { + _logger.DebugFormat("Deleting cache directory: {0}", cacheFolder); + _fileSystem.DeleteDirectory(cacheFolder); + } + + _logger.Info("Cleaning completed."); return CommandResult.Success; } - catch (Exception e) + catch (Exception ex) { - _logger.ErrorFormat("Clean failed: {0}.", e.Message); + _logger.ErrorFormat("Cleaning failed: {0}.", ex, ex.Message); return CommandResult.Error; } } } -} \ No newline at end of file +} diff --git a/src/ScriptCs/Command/CommandFactory.cs b/src/ScriptCs/Command/CommandFactory.cs index c8212869..ae1ef9e1 100644 --- a/src/ScriptCs/Command/CommandFactory.cs +++ b/src/ScriptCs/Command/CommandFactory.cs @@ -1,145 +1,200 @@ -using System.IO; +using System; +using System.IO; +using ScriptCs.Contracts; +using ScriptCs.Hosting; namespace ScriptCs.Command { public class CommandFactory { - private readonly ScriptServices _scriptServices; + private readonly IScriptServicesBuilder _scriptServicesBuilder; + private readonly IInitializationServices _initializationServices; + private readonly IFileSystem _fileSystem; - public CommandFactory(ScriptServices scriptServices) + public CommandFactory(IScriptServicesBuilder scriptServicesBuilder) { - _scriptServices = scriptServices; - } + Guard.AgainstNullArgument("scriptServicesBuilder", scriptServicesBuilder); - public ICommand CreateCommand(ScriptCsArgs args, string[] scriptArgs) - { - Guard.AgainstNullArgument("args", args); + _scriptServicesBuilder = scriptServicesBuilder; + _initializationServices = _scriptServicesBuilder.InitializationServices; + _fileSystem = _initializationServices.GetFileSystem(); + + if (_fileSystem.PackagesFile == null) + { + throw new ArgumentException( + "The file system provided by the initialization services provided by the script services builder has a null packages file.", "scriptServicesBuilder"); + } - if (args.Help) + if (_fileSystem.PackagesFolder == null) { - return new ShowUsageCommand(_scriptServices.Logger, isValid: true); + throw new ArgumentException( + "The file system provided by the initialization services provided by the script services builder has a null package folder.", "scriptServicesBuilder"); } + } + + public ICommand CreateCommand(Config config, string[] scriptArgs) + { + Guard.AgainstNullArgument("config", config); - if (args.Global) + var scriptServices = _scriptServicesBuilder.Build(); + + if (config.Global) { - var currentDir = _scriptServices.FileSystem.ModulesFolder; - if (!_scriptServices.FileSystem.DirectoryExists(currentDir)) + var currentDir = _fileSystem.GlobalFolder; + if (!_fileSystem.DirectoryExists(currentDir)) { - _scriptServices.FileSystem.CreateDirectory(currentDir); + _fileSystem.CreateDirectory(currentDir); } - _scriptServices.FileSystem.CurrentDirectory = currentDir; + _fileSystem.CurrentDirectory = currentDir; } - _scriptServices.InstallationProvider.Initialize(); + _initializationServices.GetInstallationProvider().Initialize(); - if (args.Repl) + if (config.Repl) { - var replCommand = new ExecuteReplCommand( - args.ScriptName, + var explicitReplCommand = new ExecuteReplCommand( + config.ScriptName, scriptArgs, - _scriptServices.FileSystem, - _scriptServices.ScriptPackResolver, - _scriptServices.Engine, - _scriptServices.FilePreProcessor, - _scriptServices.ObjectSerializer, - _scriptServices.Logger, - _scriptServices.Console, - _scriptServices.AssemblyResolver); - - return replCommand; + scriptServices.FileSystem, + scriptServices.ScriptPackResolver, + scriptServices.Repl, + scriptServices.LogProvider, + scriptServices.Console, + scriptServices.AssemblyResolver, + scriptServices.ScriptLibraryComposer); + + return explicitReplCommand; } - if (args.ScriptName != null) + if (config.Eval != null) { - var executeCommand = new ExecuteScriptCommand( - args.ScriptName, + var executeLooseScriptCommand = new ExecuteLooseScriptCommand( + config.Eval, scriptArgs, - _scriptServices.FileSystem, - _scriptServices.Executor, - _scriptServices.ScriptPackResolver, - _scriptServices.Logger, - _scriptServices.AssemblyResolver); - - var fileSystem = _scriptServices.FileSystem; - var currentDirectory = fileSystem.CurrentDirectory; - var packageFile = Path.Combine(currentDirectory, Constants.PackagesFile); - var packagesFolder = Path.Combine(currentDirectory, Constants.PackagesFolder); - - if (fileSystem.FileExists(packageFile) && !fileSystem.DirectoryExists(packagesFolder)) - { - var installCommand = new InstallCommand( - null, - false, - fileSystem, - _scriptServices.PackageAssemblyResolver, - _scriptServices.PackageInstaller, - _scriptServices.Logger); - - return new CompositeCommand(installCommand, executeCommand); - } - - return executeCommand; + scriptServices.FileSystem, + scriptServices.Executor, + scriptServices.ScriptPackResolver, + scriptServices.LogProvider, + scriptServices.AssemblyResolver, + scriptServices.ScriptLibraryComposer); + + return executeLooseScriptCommand; } - if (args.Install != null) + if (config.ScriptName != null) { - var installCommand = new InstallCommand( - args.Install, - args.AllowPreRelease, - _scriptServices.FileSystem, - _scriptServices.PackageAssemblyResolver, - _scriptServices.PackageInstaller, - _scriptServices.Logger); - - string currentDirectory = null; - - currentDirectory = _scriptServices.FileSystem.CurrentDirectory; - - var packageFile = Path.Combine(currentDirectory, Constants.PackagesFile); + var currentDirectory = _fileSystem.CurrentDirectory; + var packageFile = Path.Combine(currentDirectory, _fileSystem.PackagesFile); + var packagesFolder = Path.Combine(currentDirectory, _fileSystem.PackagesFolder); - if (!_scriptServices.FileSystem.FileExists(packageFile)) + if (_fileSystem.FileExists(packageFile) && !_fileSystem.DirectoryExists(packagesFolder)) { - var saveCommand = new SaveCommand(_scriptServices.PackageAssemblyResolver); - return new CompositeCommand(installCommand, saveCommand); + var installCommand = new InstallCommand( + null, + null, + true, + _fileSystem, + _initializationServices.GetPackageAssemblyResolver(), + _initializationServices.GetPackageInstaller(), + scriptServices.ScriptLibraryComposer, + _initializationServices.LogProvider); + + var executeCommand = new DeferredCreationCommand(() => + CreateScriptCommand( + config, + scriptArgs, + ScriptServicesBuilderFactory.Create(config, scriptArgs).Build())); + + return new CompositeCommand(installCommand, executeCommand); } - return installCommand; + return CreateScriptCommand(config, scriptArgs, scriptServices); } - if (args.Clean) + if (config.Clean) { - var saveCommand = new SaveCommand(_scriptServices.PackageAssemblyResolver); + var saveCommand = new SaveCommand( + _initializationServices.GetPackageAssemblyResolver(), + _fileSystem, + _initializationServices.LogProvider); - if (args.Global) + if (config.Global) { - var currentDirectory = _scriptServices.FileSystem.ModulesFolder; - _scriptServices.FileSystem.CurrentDirectory = currentDirectory; - if (!_scriptServices.FileSystem.DirectoryExists(currentDirectory)) + var currentDirectory = _fileSystem.GlobalFolder; + _fileSystem.CurrentDirectory = currentDirectory; + if (!_fileSystem.DirectoryExists(currentDirectory)) { - _scriptServices.FileSystem.CreateDirectory(currentDirectory); + _fileSystem.CreateDirectory(currentDirectory); } } - var cleanCommand = new CleanCommand( - args.ScriptName, - _scriptServices.FileSystem, - _scriptServices.Logger); + var cleanCommand = new CleanCommand(config.ScriptName, _fileSystem, _initializationServices.LogProvider); return new CompositeCommand(saveCommand, cleanCommand); } - if (args.Save) + if (config.Save) { - return new SaveCommand(_scriptServices.PackageAssemblyResolver); + return new SaveCommand( + _initializationServices.GetPackageAssemblyResolver(), + _fileSystem, + _initializationServices.LogProvider); } - if (args.Version) + if (config.PackageName != null) { - return new VersionCommand(_scriptServices.Console); + var packageAssemblyResolver = _initializationServices.GetPackageAssemblyResolver(); + + var installCommand = new InstallCommand( + config.PackageName, + config.PackageVersion, + config.AllowPreRelease, + _fileSystem, + packageAssemblyResolver, + _initializationServices.GetPackageInstaller(), + scriptServices.ScriptLibraryComposer, + _initializationServices.LogProvider); + + var saveCommand = new SaveCommand(packageAssemblyResolver, _fileSystem, _initializationServices.LogProvider); + + return new CompositeCommand(installCommand, saveCommand); } - return new ShowUsageCommand(_scriptServices.Logger, isValid: false); + // NOTE (adamralph): no script name or command so assume REPL + var replCommand = new ExecuteReplCommand( + config.ScriptName, + scriptArgs, + scriptServices.FileSystem, + scriptServices.ScriptPackResolver, + scriptServices.Repl, + scriptServices.LogProvider, + scriptServices.Console, + scriptServices.AssemblyResolver, + scriptServices.ScriptLibraryComposer); + + return replCommand; + } + + private static IScriptCommand CreateScriptCommand( + Config config, string[] scriptArgs, ScriptServices scriptServices) + { + return config.Watch + ? (IScriptCommand)new WatchScriptCommand( + config, + scriptArgs, + scriptServices.Console, + scriptServices.FileSystem, + scriptServices.LogProvider) + : new ExecuteScriptCommand( + config.ScriptName, + scriptArgs, + scriptServices.FileSystem, + scriptServices.Executor, + scriptServices.ScriptPackResolver, + scriptServices.LogProvider, + scriptServices.AssemblyResolver, + scriptServices.ScriptLibraryComposer); } } } diff --git a/src/ScriptCs/Command/CommandResult.cs b/src/ScriptCs/Command/CommandResult.cs index 3b6fbda8..e4ba13b6 100644 --- a/src/ScriptCs/Command/CommandResult.cs +++ b/src/ScriptCs/Command/CommandResult.cs @@ -2,7 +2,7 @@ { public enum CommandResult { - Success, - Error + Success = 0, + Error = 1, } } \ No newline at end of file diff --git a/src/ScriptCs/Command/CrossAppDomainExecuteScriptCommand.cs b/src/ScriptCs/Command/CrossAppDomainExecuteScriptCommand.cs new file mode 100644 index 00000000..b4ac5dca --- /dev/null +++ b/src/ScriptCs/Command/CrossAppDomainExecuteScriptCommand.cs @@ -0,0 +1,33 @@ +using System; + +namespace ScriptCs.Command +{ + [Serializable] + public class CrossAppDomainExecuteScriptCommand : ICrossAppDomainScriptCommand + { + public Config Config { get; set; } + public string[] ScriptArgs { get; set; } + public CommandResult Result { get; private set; } + + public void Execute() + { + if (this.Config == null) + { + throw new InvalidOperationException("The config is missing."); + } + + var services = ScriptServicesBuilderFactory.Create(this.Config, this.ScriptArgs).Build(); + var command = new ExecuteScriptCommand( + this.Config.ScriptName, + this.ScriptArgs, + services.FileSystem, + services.Executor, + services.ScriptPackResolver, + services.LogProvider, + services.AssemblyResolver, + services.ScriptLibraryComposer); + + this.Result = command.Execute(); + } + } +} diff --git a/src/ScriptCs/Command/DeferredCreationCommand.cs b/src/ScriptCs/Command/DeferredCreationCommand.cs new file mode 100644 index 00000000..af3953e2 --- /dev/null +++ b/src/ScriptCs/Command/DeferredCreationCommand.cs @@ -0,0 +1,18 @@ +using System; + +namespace ScriptCs.Command +{ + internal class DeferredCreationCommand : IDeferredCreationCommand where TCommand : ICommand + { + private readonly Func _factory; + + public DeferredCreationCommand(Func factory) + { + Guard.AgainstNullArgument("factory", factory); + + _factory = factory; + } + + public CommandResult Execute() => _factory().Execute(); + } +} \ No newline at end of file diff --git a/src/ScriptCs/Command/ExecuteLooseScriptCommand.cs b/src/ScriptCs/Command/ExecuteLooseScriptCommand.cs new file mode 100644 index 00000000..b6952370 --- /dev/null +++ b/src/ScriptCs/Command/ExecuteLooseScriptCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Linq; +using ScriptCs.Contracts; + +namespace ScriptCs.Command +{ + internal class ExecuteLooseScriptCommand : ExecuteScriptCommandBase, IExecuteLooseScriptCommand + { + public ExecuteLooseScriptCommand( + string script, string[] scriptArgs, + IFileSystem fileSystem, IScriptExecutor scriptExecutor, + IScriptPackResolver scriptPackResolver, + ILogProvider logProvider, + IAssemblyResolver assemblyResolver, + IScriptLibraryComposer composer) : + base(script, scriptArgs, fileSystem, scriptExecutor, scriptPackResolver, logProvider, assemblyResolver, composer) + { + } + + public override CommandResult Execute() + { + try + { + var assemblyPaths = Enumerable.Empty(); + var workingDirectory = FileSystem.CurrentDirectory; + assemblyPaths = AssemblyResolver.GetAssemblyPaths(workingDirectory); + Composer.Compose(workingDirectory); + + ScriptExecutor.Initialize(assemblyPaths, _scriptPackResolver.GetPacks()); + + // HACK: This is a (dirty) fix for #1086. This might be a temporary solution until some further refactoring can be done. + ScriptExecutor.ScriptEngine.CacheDirectory = Path.Combine(workingDirectory ?? FileSystem.CurrentDirectory, FileSystem.DllCacheFolder); + var scriptResult = ScriptExecutor.ExecuteScript(Script, ScriptArgs); + var commandResult = Inspect(scriptResult); + ScriptExecutor.Terminate(); + return commandResult; + } + catch (Exception ex) + { + Logger.ErrorException("Error executing script '{0}'", ex, Script); + return CommandResult.Error; + } + } + } +} diff --git a/src/ScriptCs/Command/ExecuteReplCommand.cs b/src/ScriptCs/Command/ExecuteReplCommand.cs index 97d782fe..fb2a2287 100644 --- a/src/ScriptCs/Command/ExecuteReplCommand.cs +++ b/src/ScriptCs/Command/ExecuteReplCommand.cs @@ -1,69 +1,81 @@ using System; -using System.Reflection; -using Common.Logging; using ScriptCs.Contracts; namespace ScriptCs.Command { - internal class ExecuteReplCommand : IScriptCommand + internal class ExecuteReplCommand : IExecuteReplCommand { - private readonly IScriptPackResolver _scriptPackResolver; - private readonly IAssemblyResolver _assemblyResolver; - private readonly IFilePreProcessor _filePreProcessor; - private readonly IObjectSerializer _serializer; - private readonly IScriptEngine _scriptEngine; private readonly string _scriptName; private readonly string[] _scriptArgs; private readonly IFileSystem _fileSystem; - private readonly IConsole _console; + private readonly IScriptPackResolver _scriptPackResolver; + private readonly IRepl _repl; private readonly ILog _logger; + private readonly IConsole _console; + private readonly IAssemblyResolver _assemblyResolver; + private readonly IScriptLibraryComposer _composer; public ExecuteReplCommand( string scriptName, string[] scriptArgs, IFileSystem fileSystem, IScriptPackResolver scriptPackResolver, - IScriptEngine scriptEngine, - IFilePreProcessor filePreProcessor, - IObjectSerializer serializer, - ILog logger, + IRepl repl, + ILogProvider logProvider, IConsole console, - IAssemblyResolver assemblyResolver) + IAssemblyResolver assemblyResolver, + IScriptLibraryComposer composer) { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("scriptPackResolver", scriptPackResolver); + Guard.AgainstNullArgument("repl", repl); + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("console", console); + Guard.AgainstNullArgument("assemblyResolver", assemblyResolver); + Guard.AgainstNullArgument("composer", composer); + _scriptName = scriptName; _scriptArgs = scriptArgs; _fileSystem = fileSystem; _scriptPackResolver = scriptPackResolver; - _scriptEngine = scriptEngine; - _filePreProcessor = filePreProcessor; - _serializer = serializer; - _logger = logger; + _repl = repl; + _logger = logProvider.ForCurrentType(); _console = console; _assemblyResolver = assemblyResolver; + _composer = composer; } - public string[] ScriptArgs { get; private set; } + public string[] ScriptArgs => _scriptArgs; public CommandResult Execute() { - _console.WriteLine("scriptcs (ctrl-c to exit)\r\n"); - var repl = new Repl(_scriptArgs, _fileSystem, _scriptEngine, _serializer, _logger, _console, _filePreProcessor); + _console.WriteLine("scriptcs (ctrl-c to exit or :help for help)" + Environment.NewLine); var workingDirectory = _fileSystem.CurrentDirectory; var assemblies = _assemblyResolver.GetAssemblyPaths(workingDirectory); var scriptPacks = _scriptPackResolver.GetPacks(); - repl.Initialize(assemblies, scriptPacks, ScriptArgs); + _composer.Compose(workingDirectory); - try + _repl.Initialize(assemblies, scriptPacks, ScriptArgs); + + if (!string.IsNullOrWhiteSpace(_scriptName)) { - if (!string.IsNullOrWhiteSpace(_scriptName)) + _logger.InfoFormat("Executing script '{0}'", _scriptName); + try + { + _repl.Execute(string.Format("#load {0}", _scriptName)); + } + catch (Exception ex) { - _logger.Info(string.Format("Loading preseeded script: {0}", _scriptName)); - repl.Execute(string.Format("#load {0}", _scriptName)); + _logger.ErrorException("Error executing script '{0}'", ex, _scriptName); + return CommandResult.Error; } + } - while (ExecuteLine(repl)) + try + { + while (ExecuteLine(_repl)) { } @@ -71,38 +83,36 @@ public CommandResult Execute() } catch (Exception ex) { - _logger.Error(ex.Message); + _logger.ErrorException("Error executing REPL", ex); return CommandResult.Error; } - repl.Terminate(); + _repl.Terminate(); return CommandResult.Success; } - private bool ExecuteLine(Repl repl) + private bool ExecuteLine(IRepl repl) { - if (string.IsNullOrWhiteSpace(repl.Buffer)) - { - _console.Write("> "); - } - - string line = null; + var prompt = string.IsNullOrWhiteSpace (repl.Buffer) ? "> " : "* "; try { - line = _console.ReadLine(); + var line = _console.ReadLine(prompt); + + if (line == null) + return false; + + if (!string.IsNullOrWhiteSpace(line)) + { + repl.Execute(line); + } + + return true; } catch { return false; } - - if (!string.IsNullOrWhiteSpace(line)) - { - repl.Execute(line); - } - - return true; } } } diff --git a/src/ScriptCs/Command/ExecuteScriptCommand.cs b/src/ScriptCs/Command/ExecuteScriptCommand.cs index 10ec81b0..f55100f9 100644 --- a/src/ScriptCs/Command/ExecuteScriptCommand.cs +++ b/src/ScriptCs/Command/ExecuteScriptCommand.cs @@ -1,86 +1,49 @@ using System; using System.IO; using System.Linq; -using Common.Logging; - using ScriptCs.Contracts; namespace ScriptCs.Command { - internal class ExecuteScriptCommand : IScriptCommand + internal class ExecuteScriptCommand : ExecuteScriptCommandBase, IScriptCommand { - private readonly IScriptPackResolver _scriptPackResolver; - - private readonly IAssemblyResolver _assemblyResolver; - - private readonly IScriptExecutor _scriptExecutor; - - private readonly IFileSystem _fileSystem; - - private readonly string _script; - - private readonly ILog _logger; - - public ExecuteScriptCommand(string script, - string[] scriptArgs, - IFileSystem fileSystem, - IScriptExecutor scriptExecutor, - IScriptPackResolver scriptPackResolver, - ILog logger, - IAssemblyResolver assemblyResolver) + public ExecuteScriptCommand( + string script, string[] scriptArgs, + IFileSystem fileSystem, IScriptExecutor scriptExecutor, + IScriptPackResolver scriptPackResolver, + ILogProvider logProvider, + IAssemblyResolver assemblyResolver, + IScriptLibraryComposer composer) : + base(script, scriptArgs, fileSystem, scriptExecutor, scriptPackResolver, logProvider, assemblyResolver, composer) { - _script = script; - _fileSystem = fileSystem; - ScriptArgs = scriptArgs; - _scriptExecutor = scriptExecutor; - _scriptPackResolver = scriptPackResolver; - _logger = logger; - _assemblyResolver = assemblyResolver; } - public string[] ScriptArgs { get; private set; } - - public CommandResult Execute() + public override CommandResult Execute() { try { var assemblyPaths = Enumerable.Empty(); - - var workingDirectory = _fileSystem.GetWorkingDirectory(_script); + var workingDirectory = FileSystem.GetWorkingDirectory(Script); if (workingDirectory != null) { - assemblyPaths = _assemblyResolver.GetAssemblyPaths(workingDirectory); + assemblyPaths = AssemblyResolver.GetAssemblyPaths(workingDirectory); } - _scriptExecutor.Initialize(assemblyPaths, _scriptPackResolver.GetPacks(), ScriptArgs); - var result = _scriptExecutor.Execute(_script, ScriptArgs); - _scriptExecutor.Terminate(); + Composer.Compose(workingDirectory); - if (result == null) return CommandResult.Error; + ScriptExecutor.Initialize(assemblyPaths, _scriptPackResolver.GetPacks(), ScriptArgs); - if (result.CompileExceptionInfo != null) - { - _logger.Error(result.CompileExceptionInfo.SourceException.Message); - _logger.Debug(result.CompileExceptionInfo.SourceException); - return CommandResult.Error; - } - - if (result.ExecuteExceptionInfo != null) - { - _logger.Error(result.ExecuteExceptionInfo.SourceException); - return CommandResult.Error; - } + // HACK: This is a (dirty) fix for #1086. This might be a temporary solution until some further refactoring can be done. + ScriptExecutor.ScriptEngine.CacheDirectory = Path.Combine(workingDirectory ?? FileSystem.CurrentDirectory, FileSystem.DllCacheFolder); + var scriptResult = ScriptExecutor.Execute(Script, ScriptArgs); - return CommandResult.Success; - } - catch (FileNotFoundException fnfex) - { - _logger.ErrorFormat("{0} - {1}", fnfex.Message, fnfex.FileName); - return CommandResult.Error; + var commandResult = Inspect(scriptResult); + ScriptExecutor.Terminate(); + return commandResult; } catch (Exception ex) { - _logger.Error(ex.Message); + Logger.ErrorException("Error executing script '{0}'", ex, Script); return CommandResult.Error; } } diff --git a/src/ScriptCs/Command/FileWatcher.cs b/src/ScriptCs/Command/FileWatcher.cs new file mode 100644 index 00000000..e590439b --- /dev/null +++ b/src/ScriptCs/Command/FileWatcher.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using ScriptCs.Contracts; + +namespace ScriptCs.Command +{ + public sealed class FileWatcher : IDisposable + { + private readonly object _timerLock = new object(); + private readonly string _file; + private readonly int _intervalMilliseconds; + private readonly IFileSystem _fileSystem; + + private DateTime _lastWriteTime; + private Timer _timer; + + public FileWatcher(string file, int intervalMilliseconds, IFileSystem fileSystem) + { + Guard.AgainstNullArgument("fileSystem", fileSystem); + + _file = file; + _intervalMilliseconds = intervalMilliseconds; + _fileSystem = fileSystem; + } + + public event EventHandler Changed; + + public void Start() + { + lock (_timerLock) + { + if (_timer != null) + { + return; + } + + _lastWriteTime = _fileSystem.GetLastWriteTime(_file); + _timer = new Timer(_ => CheckLastWriteTime(), null, Timeout.Infinite, Timeout.Infinite); + _timer.Change(_intervalMilliseconds, Timeout.Infinite); + } + } + + public void Stop() + { + lock (_timerLock) + { + if (_timer == null) + { + return; + } + + _timer.Dispose(); + _timer = null; + } + } + + public void Dispose() + { + Stop(); + } + + private void CheckLastWriteTime() + { + lock (_timerLock) + { + if (_timer == null) + { + return; + } + + var previousLastWriteTime = _lastWriteTime; + _lastWriteTime = _fileSystem.GetLastWriteTime(_file); + if (_lastWriteTime != previousLastWriteTime) + { + var changed = this.Changed; + if (changed != null) + { + changed.Invoke(this, EventArgs.Empty); + } + } + + _timer.Change(_intervalMilliseconds, Timeout.Infinite); + } + } + } +} diff --git a/src/ScriptCs/Command/ICommand.cs b/src/ScriptCs/Command/ICommand.cs index 7ad2f912..705eb21f 100644 --- a/src/ScriptCs/Command/ICommand.cs +++ b/src/ScriptCs/Command/ICommand.cs @@ -2,28 +2,28 @@ namespace ScriptCs.Command { - public interface IScriptCommand : ICommand + public interface IScriptCommand : ICommand { string[] ScriptArgs { get; } } - public interface ISaveCommand : ICommand + public interface IExecuteReplCommand : IScriptCommand { } - public interface ICleanCommand : ICommand + public interface IExecuteLooseScriptCommand : ICommand { } - public interface IInstallCommand : ICommand + public interface ISaveCommand : ICommand { } - public interface IInvalidCommand : ICommand + public interface ICleanCommand : ICommand { } - public interface IHelpCommand : ICommand + public interface IInstallCommand : ICommand { } @@ -32,7 +32,7 @@ public interface ICompositeCommand : ICommand List Commands { get; } } - public interface IVersionCommand : ICommand + public interface IDeferredCreationCommand : ICommand where TCommand : ICommand { } @@ -40,4 +40,16 @@ public interface ICommand { CommandResult Execute(); } + + public interface ICrossAppDomainCommand + { + CommandResult Result { get; } + + void Execute(); + } + + public interface ICrossAppDomainScriptCommand : ICrossAppDomainCommand + { + string[] ScriptArgs { get; } + } } \ No newline at end of file diff --git a/src/ScriptCs/Command/InstallCommand.cs b/src/ScriptCs/Command/InstallCommand.cs index 7d0320d5..d1e960dc 100644 --- a/src/ScriptCs/Command/InstallCommand.cs +++ b/src/ScriptCs/Command/InstallCommand.cs @@ -2,61 +2,72 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Versioning; -using Common.Logging; - using ScriptCs.Contracts; -using ScriptCs.Package; +using ScriptCs.Hosting.Package; namespace ScriptCs.Command { internal class InstallCommand : IInstallCommand { private readonly string _name; - + private readonly string _version; private readonly bool _allowPre; - private readonly IFileSystem _fileSystem; - private readonly IPackageAssemblyResolver _packageAssemblyResolver; - private readonly IPackageInstaller _packageInstaller; - + private readonly IScriptLibraryComposer _composer; private readonly ILog _logger; public InstallCommand( string name, + string version, bool allowPre, IFileSystem fileSystem, IPackageAssemblyResolver packageAssemblyResolver, IPackageInstaller packageInstaller, - ILog logger) + IScriptLibraryComposer composer, + ILogProvider logger) { _name = name; + _version = version ?? string.Empty; _allowPre = allowPre; _fileSystem = fileSystem; _packageAssemblyResolver = packageAssemblyResolver; _packageInstaller = packageInstaller; - _logger = logger; + _composer = composer; + _logger = logger.ForCurrentType(); } public CommandResult Execute() { - var workingDirectory = _fileSystem.CurrentDirectory; _logger.Info("Installing packages..."); - _logger.TraceFormat("Packages folder: {0}", Path.Combine(workingDirectory, "Packages")); - - var packages = GetPackages(workingDirectory); - + + var packagesFolder = Path.Combine(_fileSystem.CurrentDirectory, _fileSystem.PackagesFolder); + if (!string.IsNullOrWhiteSpace(_composer.ScriptLibrariesFile)) + { + var scriptLibrariesFile = Path.Combine(packagesFolder, _composer.ScriptLibrariesFile); + + if (_fileSystem.FileExists(scriptLibrariesFile)) + { + _logger.DebugFormat("Deleting: {0}", scriptLibrariesFile); + _fileSystem.FileDelete(scriptLibrariesFile); + } + } + + var packages = GetPackages(_fileSystem.CurrentDirectory); try { _packageInstaller.InstallPackages(packages, _allowPre); - - _logger.Info("Installation completed successfully."); + _logger.Info("Package installation succeeded."); return CommandResult.Success; } - catch (Exception e) + catch (Exception ex) { - _logger.ErrorFormat("Installation failed: {0}.", e.Message); + if (ex is AggregateException agrEx) + { + ex = agrEx.Flatten().InnerException; + } + _logger.ErrorException("Package installation failed.", ex); return CommandResult.Error; } } @@ -74,7 +85,7 @@ private IEnumerable GetPackages(string workingDirectory) yield break; } - yield return new PackageReference(_name, new FrameworkName(".NETFramework,Version=v4.0"), new Version()); + yield return new PackageReference(_name, new FrameworkName(FrameworkUtils.FrameworkName), _version); } } } \ No newline at end of file diff --git a/src/ScriptCs/Command/SaveCommand.cs b/src/ScriptCs/Command/SaveCommand.cs index e9cd0ed7..70819d6a 100644 --- a/src/ScriptCs/Command/SaveCommand.cs +++ b/src/ScriptCs/Command/SaveCommand.cs @@ -7,21 +7,31 @@ internal class SaveCommand : ISaveCommand { private readonly IPackageAssemblyResolver _packageAssemblyResolver; - public SaveCommand(IPackageAssemblyResolver packageAssemblyResolver) + private readonly IFileSystem _fileSystem; + private readonly ILog _logger; + + public SaveCommand(IPackageAssemblyResolver packageAssemblyResolver, IFileSystem fileSystem, ILogProvider logProvider) { + Guard.AgainstNullArgument("packageAssemblyResolver", packageAssemblyResolver); + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("logProvider", logProvider); + _packageAssemblyResolver = packageAssemblyResolver; + _fileSystem = fileSystem; + _logger = logProvider.ForCurrentType(); } public CommandResult Execute() { - Console.WriteLine("Initiated saving packages into packages.config..."); + _logger.InfoFormat("Saving packages in {0}...", _fileSystem.PackagesFile); + try { _packageAssemblyResolver.SavePackages(); } - catch (Exception e) + catch (Exception ex) { - Console.WriteLine("Save failed: {0}.", e.Message); + _logger.ErrorFormat("Package saving failed: {0}.", ex, ex.Message); return CommandResult.Error; } diff --git a/src/ScriptCs/Command/ShowUsageCommand.cs b/src/ScriptCs/Command/ShowUsageCommand.cs deleted file mode 100644 index ebf2bccb..00000000 --- a/src/ScriptCs/Command/ShowUsageCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Common.Logging; -using PowerArgs; - -namespace ScriptCs.Command -{ - internal class ShowUsageCommand : IHelpCommand, IInvalidCommand - { - private readonly ILog _logger; - - private readonly bool _isValid; - - public ShowUsageCommand(ILog logger, bool isValid) - { - _logger = logger; - _isValid = isValid; - } - - public CommandResult Execute() - { - var options = new ArgUsageOptions { ShowPosition = false, ShowType = false }; - var usage = ArgUsage.GetUsage(options: options); - - if (_isValid) - { - _logger.Info(usage); - return CommandResult.Success; - } - - _logger.Error(usage); - return CommandResult.Error; - } - } -} diff --git a/src/ScriptCs/Command/VersionCommand.cs b/src/ScriptCs/Command/VersionCommand.cs deleted file mode 100644 index 1ed1e9b2..00000000 --- a/src/ScriptCs/Command/VersionCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics; -using ScriptCs.Contracts; - -namespace ScriptCs.Command -{ - internal class VersionCommand : IVersionCommand - { - private readonly IConsole _console; - - public VersionCommand(IConsole console) - { - _console = console; - } - - public CommandResult Execute() - { - var assembly = typeof(Program).Assembly; - var productVersion = FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion; - var message = string.Format("scriptcs v{0}", productVersion); - - _console.WriteLine(message); - - return CommandResult.Success; - } - } -} diff --git a/src/ScriptCs/Command/WatchScriptCommand.cs b/src/ScriptCs/Command/WatchScriptCommand.cs new file mode 100644 index 00000000..3c1a0f44 --- /dev/null +++ b/src/ScriptCs/Command/WatchScriptCommand.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading; +using ScriptCs.Contracts; + +namespace ScriptCs.Command +{ + internal class WatchScriptCommand : IScriptCommand + { + private readonly AppDomainSetup _setup = new AppDomainSetup + { + ApplicationBase = AppDomain.CurrentDomain.BaseDirectory + }; + + private readonly Config _config; + private readonly string[] _scriptArgs; + private readonly IConsole _console; + private readonly IFileSystem _fileSystem; + private readonly ILog _logger; + private readonly CrossAppDomainExecuteScriptCommand _executeScriptCommand; + + public WatchScriptCommand( + Config config, + string[] scriptArgs, + IConsole console, + IFileSystem fileSystem, + ILogProvider logProvider) + { + Guard.AgainstNullArgument("config", config); + Guard.AgainstNullArgument("scriptArgs", scriptArgs); + Guard.AgainstNullArgument("console", console); + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("logProvider", logProvider); + + _config = config; + _scriptArgs = scriptArgs; + _console = console; + _fileSystem = fileSystem; + _logger = logProvider.ForCurrentType(); + + _executeScriptCommand = new CrossAppDomainExecuteScriptCommand + { + Config = _config, + ScriptArgs = _scriptArgs, + }; + } + + public CommandResult Execute() + { + _console.WriteLine("scriptcs (ctrl-c to exit)"); + _logger.InfoFormat("Running script '{0}' and watching for changes...", _config.ScriptName); + + while (true) + { + using (var fileChanged = new ManualResetEventSlim()) + using (var watcher = new FileWatcher(_config.ScriptName, 500, _fileSystem)) + { + _logger.DebugFormat("Creating app domain '{0}'...", _config.ScriptName); + var appDomain = AppDomain.CreateDomain(_config.ScriptName, null, _setup); + try + { + watcher.Changed += (sender, e) => + { + _logger.DebugFormat("Script '{0}' changed.", _config.ScriptName); + EnsureUnloaded(appDomain); + fileChanged.Set(); + }; + + watcher.Start(); + _logger.DebugFormat("Executing script '{0}' and watching for changes...", _config.ScriptName); + fileChanged.Reset(); + try + { + appDomain.DoCallBack(_executeScriptCommand.Execute); + } + catch (AppDomainUnloadedException ex) + { + _logger.DebugFormat("App domain '{0}' has been unloaded.", ex, _config.ScriptName); + } + } + finally + { + EnsureUnloaded(appDomain); + } + + fileChanged.Wait(); + _logger.InfoFormat("Script changed. Reloading...", _config.ScriptName); + } + } + } + + private void EnsureUnloaded(AppDomain domain) + { + try + { + _logger.DebugFormat("Unloading app domain '{0}'", _config.ScriptName); + AppDomain.Unload(domain); + } + catch (AppDomainUnloadedException ex) + { + _logger.DebugFormat("App domain '{0}' has already been unloaded.", ex, _config.ScriptName); + } + } + + public string[] ScriptArgs => this._scriptArgs; + } +} diff --git a/src/ScriptCs/Config.cs b/src/ScriptCs/Config.cs new file mode 100644 index 00000000..f3cc226e --- /dev/null +++ b/src/ScriptCs/Config.cs @@ -0,0 +1,106 @@ +using System; +using ScriptCs.Contracts; +using System.IO; + +namespace ScriptCs +{ + // NOTE (Adam): passed across app domains as a property of CrossAppDomainExecuteScriptCommand + [Serializable] + public class Config + { + private string[] _modules; + + public Config() + { + LogLevel = LogLevel.Info; + } + + // global + public LogLevel LogLevel { get; set; } + + public string[] Modules + { + get => _modules ?? new string[0]; + set => _modules = value; + } + + public string OutputFile { get; set; } + + // clean command + public bool Clean { get; set; } + + // install command + public bool AllowPreRelease { get; set; } + + public bool Global { get; set; } + + public string PackageName { get; set; } + + public string PackageVersion { get; set; } + + // save command + public bool Save { get; set; } + + // run command + public string ScriptName { get; set; } + + public bool Cache { get; set; } + + public bool Debug { get; set; } + + public bool Repl { get; set; } + + public bool Watch { get; set; } + + public string Eval { get; set; } + + public static Config Create(string maskFile, ConfigMask configMask) + { + Guard.AgainstNullArgument("configMask", configMask); + + return new Config() + .Apply(ConfigMask.ReadGlobalOrDefault()) + .Apply(maskFile == null ? ConfigMask.ReadLocalOrDefault() : ConfigMask.Read(maskFile)) + .Apply(configMask); + } + + public Config Apply(ConfigMask mask) + { + if (mask == null) + { + return this; + } + + var logLevel = mask.Debug.GetValueOrDefault() && !mask.LogLevel.HasValue && LogLevel != LogLevel.Trace + ? LogLevel.Debug + : mask.LogLevel; + + var modules = mask.Modules == null + ? null + : mask.Modules.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries); + + var scriptName = mask.ScriptName != null && !Path.GetFileName(mask.ScriptName).Contains(".") + ? Path.ChangeExtension(mask.ScriptName, "csx") + : mask.ScriptName; + + return new Config + { + LogLevel = logLevel ?? LogLevel, + _modules = modules ?? _modules, + OutputFile = mask.Output ?? OutputFile, + Clean = mask.Clean ?? Clean, + AllowPreRelease = mask.AllowPreRelease ?? AllowPreRelease, + Global = mask.Global ?? Global, + PackageName = mask.Install ?? PackageName, + PackageVersion = mask.PackageVersion ?? PackageVersion, + Save = mask.Save ?? Save, + Cache = mask.Cache ?? Cache, + Debug = mask.Debug ?? Debug, + Repl = mask.Repl ?? Repl, + ScriptName = scriptName ?? ScriptName, + Eval = mask.Eval ?? Eval, + Watch = mask.Watch ?? Watch, + }; + } + } +} diff --git a/src/ScriptCs/ConfigMask.cs b/src/ScriptCs/ConfigMask.cs new file mode 100644 index 00000000..53120ea4 --- /dev/null +++ b/src/ScriptCs/ConfigMask.cs @@ -0,0 +1,69 @@ +using System; +using System.Globalization; +using System.IO; +using Newtonsoft.Json; + +namespace ScriptCs +{ + using ScriptCs.Contracts; + + public class ConfigMask + { + public bool? AllowPreRelease { get; set; } + + public bool? Cache { get; set; } + + public bool? Clean { get; set; } + + public bool? Debug { get; set; } + + public bool? Global { get; set; } + + public string Install { get; set; } + + public LogLevel? LogLevel { get; set; } + + public string Modules { get; set; } + + public string Output { get; set; } + + public string PackageVersion { get; set; } + + public bool? Repl { get; set; } + + public bool? Save { get; set; } + + public string ScriptName { get; set; } + + public string Eval { get; set; } + + public bool? Watch { get; set; } + + public static ConfigMask ReadGlobalOrDefault() => Read(new FileSystem().GlobalOptsFile, true); + + public static ConfigMask ReadLocalOrDefault() => Read(Constants.ConfigFilename, true); + + public static ConfigMask Read(string path) => Read(path, false); + + private static ConfigMask Read(string path, bool defaultIfNotExists) + { + if (defaultIfNotExists && !File.Exists(path)) + { + return null; + } + + var json = File.ReadAllText(path); + try + { + return JsonConvert.DeserializeObject(json); + } + catch (Exception ex) + { + var message = string.Format( + CultureInfo.InvariantCulture, "Error reading JSON config from '{0}'.", path); + + throw new InvalidOperationException(message, ex); + } + } + } +} diff --git a/src/ScriptCs/ExecuteScriptCommandBase.cs b/src/ScriptCs/ExecuteScriptCommandBase.cs new file mode 100644 index 00000000..2c98b677 --- /dev/null +++ b/src/ScriptCs/ExecuteScriptCommandBase.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ScriptCs.Command; +using ScriptCs.Contracts; + +namespace ScriptCs +{ + public abstract class ExecuteScriptCommandBase + { + protected string Script { get; private set; } + protected IFileSystem FileSystem { get; private set; } + protected IScriptExecutor ScriptExecutor { get; private set; } + protected IScriptPackResolver _scriptPackResolver { get; private set; } + protected ILog Logger { get; private set; } + protected IAssemblyResolver AssemblyResolver { get; private set; } + protected IScriptLibraryComposer Composer { get; private set; } + + public ExecuteScriptCommandBase( + string script, + string[] scriptArgs, + IFileSystem fileSystem, + IScriptExecutor scriptExecutor, + IScriptPackResolver scriptPackResolver, + ILogProvider logProvider, + IAssemblyResolver assemblyResolver, + IScriptLibraryComposer composer + ) + { + Guard.AgainstNullArgument("fileSystem", fileSystem); + Guard.AgainstNullArgument("scriptExecutor", scriptExecutor); + Guard.AgainstNullArgument("scriptPackResolver", scriptPackResolver); + Guard.AgainstNullArgument("logProvider", logProvider); + Guard.AgainstNullArgument("assemblyResolver", assemblyResolver); + Guard.AgainstNullArgument("composer", composer); + + Script = script; + ScriptArgs = scriptArgs; + FileSystem = fileSystem; + ScriptExecutor = scriptExecutor; + _scriptPackResolver = scriptPackResolver; + Logger = logProvider.ForCurrentType(); + AssemblyResolver = assemblyResolver; + Composer = composer; + } + + public string[] ScriptArgs { get; private set; } + + public abstract CommandResult Execute(); + + protected CommandResult Inspect(ScriptResult result) + { + if (result == null) + { + return CommandResult.Error; + } + + if (result.CompileExceptionInfo != null) + { + var ex = result.CompileExceptionInfo.SourceException; + Logger.ErrorException("Script compilation failed.", ex); + return CommandResult.Error; + } + + if (result.ExecuteExceptionInfo != null) + { + var ex = result.ExecuteExceptionInfo.SourceException; + Logger.ErrorException("Script execution failed.", ex); + return CommandResult.Error; + } + + if (!result.IsCompleteSubmission) + { + Logger.Error("The script is incomplete."); + return CommandResult.Error; + } + + return CommandResult.Success; + } + } +} diff --git a/src/ScriptCs/ProfileOptimizationShim.cs b/src/ScriptCs/ProfileOptimizationShim.cs new file mode 100644 index 00000000..ff83bffc --- /dev/null +++ b/src/ScriptCs/ProfileOptimizationShim.cs @@ -0,0 +1,30 @@ +using System; +using System.Reflection; + +namespace ScriptCs +{ + internal static class ProfileOptimizationShim + { + private static readonly Type profileOptimization = Type.GetType("System.Runtime.ProfileOptimization"); + + public static void SetProfileRoot(string directoryPath) + { + if (profileOptimization != null) + { + profileOptimization + .GetMethod("SetProfileRoot", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, new object[] { directoryPath }); + } + } + + public static void StartProfile(string profile) + { + if (profileOptimization != null) + { + profileOptimization + .GetMethod("StartProfile", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, new object[] { profile }); + } + } + } +} diff --git a/src/ScriptCs/Program.cs b/src/ScriptCs/Program.cs index 9c804025..789353ee 100644 --- a/src/ScriptCs/Program.cs +++ b/src/ScriptCs/Program.cs @@ -1,79 +1,114 @@ -using System.IO; -using System.Runtime; -using ScriptCs.Argument; -using ScriptCs.Command; +using McMaster.Extensions.CommandLineUtils; +using ScriptCs.Contracts; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; namespace ScriptCs { internal static class Program { + [LoaderOptimizationAttribute(LoaderOptimization.MultiDomain)] private static int Main(string[] args) { - ProfileOptimization.SetProfileRoot(typeof(Program).Assembly.Location); - ProfileOptimization.StartProfile(typeof(Program).Assembly.GetName().Name + ".profile"); + //args = args.Skip(2).ToArray(); + ProfileOptimizationShim.SetProfileRoot(Path.GetDirectoryName(typeof(Program).Assembly.Location)); + ProfileOptimizationShim.StartProfile(typeof(Program).Assembly.GetName().Name + ".profile"); - var console = new ScriptConsole(); + var nonScriptArgs = args.TakeWhile(arg => arg != "--").ToArray(); + var scriptArgs = args.Skip(nonScriptArgs.Length + 1).ToArray(); - var parser = new ArgumentHandler(new ArgumentParser(console), new ConfigFileParser(console), new FileSystem()); - var arguments = parser.Parse(args); - var commandArgs = arguments.CommandArguments; - var scriptArgs = arguments.ScriptArguments; + var app = new CommandLineApplication(throwOnUnexpectedArg: true) + { + ExtendedHelpText = "Usage: scriptcs options", + OptionsComparison = StringComparison.OrdinalIgnoreCase + }; - var configurator = new LoggerConfigurator(commandArgs.LogLevel); - configurator.Configure(console); - var logger = configurator.GetLogger(); - - var scriptServicesBuilder = new ScriptServicesBuilder(console, logger) - .Cache(commandArgs.Cache) - .LogLevel(commandArgs.LogLevel) - .ScriptName(commandArgs.ScriptName) - .Repl(commandArgs.Repl); + var script = app.Argument("script", "Script file name, must be specified first"); - var modules = GetModuleList(commandArgs.Modules); - var extension = Path.GetExtension(commandArgs.ScriptName); + var scriptNameFallback = app.Option("--scriptname | -script", "Alternative way to pass a script filename", CommandOptionType.NoValue); + var repl = app.Option("--repl | -r", "Launch REPL mode when running script. To just launch REPL, simply omit the 'script' argument", CommandOptionType.NoValue); + var eval = app.Option("--eval | -e", "Code to immediately evaluate", CommandOptionType.SingleValue); + var configFile = app.Option("--config | -co", "Defines config file name", CommandOptionType.SingleValue); + var debug = app.Option("--debug | -d", "Emits PDB symbols allowing for attaching a Visual Studio debugger", CommandOptionType.NoValue); + var version = app.Option("--version | -v", "Outputs version information", CommandOptionType.NoValue); + var cache = app.Option("--cache | -c", "Flag which determines whether to run in memory or from a .dll", CommandOptionType.NoValue); + var logLevel = app.Option("--loglevel | -log", "Flag which defines the log level used", CommandOptionType.SingleValue); + var watch = app.Option("--watch | -w", "Watch the script file and reload it when changed", CommandOptionType.NoValue); + var modules = app.Option("--modules | -m", "Specify modules to load (comma separated)", CommandOptionType.SingleValue); + var output = app.Option("--output | -o", "Write all console output to the specified file", CommandOptionType.SingleValue); + app.HelpOption("-Help | -?"); - if (string.IsNullOrWhiteSpace(extension) && !commandArgs.Repl) + app.Command("install", c => { - // No extension was given, i.e we might have something like - // "scriptcs foo" to deal with. We activate the default extension, - // to make sure it's given to the LoadModules below. - extension = ".csx"; + var package = c.Argument("package", "Specific package to install, otherwise installs and restores packages which are specified in packages.config"); + var allowPrerelease = c.Option("--allowprerelease | -pre", "Allows installation of packages' prelease versions", CommandOptionType.NoValue); + var packageVersion = c.Option("--packageversion | -p", "Defines the version of the package to install", CommandOptionType.SingleValue); + var save = c.Option("--save | -s", "Creates a packages.config file based on the packages directory", CommandOptionType.NoValue); + var clean = c.Option("--clean | -cl", "Cleans installed packages from working directory", CommandOptionType.NoValue); + var global = c.Option("--global | -g", "Installs and restores global packages which are specified in packages.config", CommandOptionType.NoValue); - if (!string.IsNullOrWhiteSpace(commandArgs.ScriptName)) + c.OnExecute(() => { - // If the was in fact a script specified, we'll extend it - // with the default extension, assuming the user giving - // "scriptcs foo" actually meant "scriptcs foo.csx". We - // perform no validation here thought; let it be done by - // the activated command. If the file don't exist, it's - // up to the command to detect and report. + var configMask = new ConfigMask + { + Repl = false, + Global = global.HasValue() ? true : (bool?)null, + Install = package.Value ?? string.Empty, // needed to trigger install of all packages! + PackageVersion = packageVersion.Value(), + AllowPreRelease = allowPrerelease.HasValue() ? true : (bool?)null, + Save = save.HasValue() ? true : (bool?)null, + Clean = clean.HasValue() ? true : (bool?)null, + Output = output.Value(), + Debug = debug.HasValue() ? true : (bool?)null, + LogLevel = logLevel.ParsedValue + }; - commandArgs.ScriptName += extension; - } - } - - scriptServicesBuilder.LoadModules(extension, modules); - var scriptServiceRoot = scriptServicesBuilder.Build(); + return Application.Run(Config.Create(configFile.Value(), configMask), scriptArgs); + }); + }); - var commandFactory = new CommandFactory(scriptServiceRoot); - var command = commandFactory.CreateCommand(commandArgs, scriptArgs); + app.OnExecute(() => + { + if (configFile.HasValue() && !File.Exists(configFile.Value())) + { + Console.WriteLine("The specified config file does not exist."); + return 1; + } - var result = command.Execute(); + if (version.HasValue()) + { + VersionWriter.Write(); + return 0; + } - return result == CommandResult.Success ? 0 : -1; - } + var configMask = new ConfigMask + { + Repl = repl.HasValue() ? true : (bool?)null, + ScriptName = scriptNameFallback.HasValue() ? scriptNameFallback.Value() : script.Value, + Debug = debug.HasValue() ? true : (bool?)null, + Eval = eval.Value(), + Cache = cache.HasValue() ? true : (bool?)null, + Watch = watch.HasValue() ? true : (bool?)null, + LogLevel = logLevel.ParsedValue, + Modules = modules.Value(), + Output = output.Value() + }; - private static string[] GetModuleList(string modulesArg) - { - var modules = new string[0]; + return Application.Run(Config.Create(configFile.Value(), configMask), scriptArgs); + }); - if (modulesArg != null) + try { - modules = modulesArg.Split(','); + return app.Execute(nonScriptArgs); + } + catch (CommandParsingException) + { + app.ShowHelp(); + return 1; } - - return modules; } } -} \ No newline at end of file +} diff --git a/src/ScriptCs/Properties/AssemblyInfo.cs b/src/ScriptCs/Properties/AssemblyInfo.cs index dfee66f6..707f917b 100644 --- a/src/ScriptCs/Properties/AssemblyInfo.cs +++ b/src/ScriptCs/Properties/AssemblyInfo.cs @@ -1,10 +1,8 @@ using System.Reflection; using System.Resources; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("scriptcs")] -[assembly: AssemblyDescription("scriptcs command line tool.")] - [assembly: Guid("f624ee58-910a-4541-a46f-afcd3edca9df")] - [assembly: NeutralResourcesLanguage("en-US")] +[assembly: InternalsVisibleTo("ScriptCs.Tests")] diff --git a/src/ScriptCs/Properties/Settings.Designer.cs b/src/ScriptCs/Properties/Settings.Designer.cs index 0f094d02..3d2dfa89 100644 --- a/src/ScriptCs/Properties/Settings.Designer.cs +++ b/src/ScriptCs/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18051 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,7 +12,7 @@ namespace ScriptCs.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/src/ScriptCs/Properties/chocolateyInstall.ps1 b/src/ScriptCs/Properties/chocolateyInstall.ps1 index c8c1caba..85c2846b 100644 --- a/src/ScriptCs/Properties/chocolateyInstall.ps1 +++ b/src/ScriptCs/Properties/chocolateyInstall.ps1 @@ -1,34 +1,43 @@ -try { +try { $tools = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" - $nuget = "$env:ChocolateyInstall\ChocolateyInstall\nuget" - $binPath = "$env:APPDATA\scriptcs" - $nugetPath = "$tools\nugets" - New-Item $binPath -ItemType Directory -Force | Out-Null + if (Test-Path "$tools\..\lib") { + Remove-Item "$tools\..\lib" -Recurse -Force + } - Copy-Item "$tools\scriptcs\*" $binPath -Force + # Handle upgrade from previous packages that installed to the %AppData%/scriptcs folders. + $oldPaths = @( + "$env:APPDATA\scriptcs", + "$env:LOCALAPPDATA\scriptcs" + ) - Write-Host "Retrieving NuGet dependencies..." -ForegroundColor DarkYellow + $oldPaths | foreach { + # Remove the old user-specific scriptcs folders. + if (Test-Path $_) { + Remove-Item $_ -Recurse -Force + } - $dependencies = @{ - "Roslyn.Compilers.CSharp" = "1.2.20906.2"; - } + # Remove the user-specific path that got added in previous installs. + # There's no Uninstall-ChocolateyPath yet so we need to do it manually. + # https://github.com/chocolatey/chocolatey/issues/97 + $envPath = $env:PATH + if ($envPath.ToLower().Contains($_.ToLower())) { + $userPath = [Environment]::GetEnvironmentVariable("Path","User") + if($userPath) { + $actualPath = [System.Collections.ArrayList]($userPath).Split(";") + $actualPath.Remove($_) + $newPath = $actualPath -Join ";" + [Environment]::SetEnvironmentVariable("Path", $newPath, "User") + } + } - $dependencies.GetEnumerator() | %{ &nuget install $_.Name -version $_.Value -o $nugetPath } - - Get-ChildItem $nugetPath -Filter "*.dll" -Recurse | %{ Copy-Item $_.FullName $binPath -Force } - Remove-Item $nugetPath -Recurse -Force - New-Item "$tools\scriptcs\scriptcs.exe.ignore" -ItemType File -Force | Out-Null - - if (Test-Path "$tools\..\lib") { - Remove-Item "$tools\..\lib" -Recurse -Force + Write-Host "'$_' has been removed." -ForegroundColor DarkYellow } + Update-SessionEnvironment + # End upgrade handling. - Install-ChocolateyPath $binPath - Write-Host "scriptcs.exe has been installed to $binpath and has been added to your path." -ForegroundColor DarkYellow - Write-Host "You may need to open a new console for the new path to take effect. Happy scripting!" -ForegroundColor DarkYellow Write-ChocolateySuccess 'scriptcs' } catch { Write-ChocolateyFailure 'scriptcs' "$($_.Exception.Message)" - throw + throw } \ No newline at end of file diff --git a/src/ScriptCs/Properties/chocolateyUninstall.ps1 b/src/ScriptCs/Properties/chocolateyUninstall.ps1 deleted file mode 100644 index 549e695b..00000000 --- a/src/ScriptCs/Properties/chocolateyUninstall.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -try { - $paths = @( - "$env:APPDATA\scriptcs", - "$env:LOCALAPPDATA\scriptcs" - ) - - $paths | foreach { - if (Test-Path $_) { - Remove-Item $_ -Recurse -Force - } - - Write-Host "'$_' has been removed." -ForegroundColor DarkYellow - } - - Write-ChocolateySuccess 'scriptcs' -} catch { - Write-ChocolateyFailure 'scriptcs' "$($_.Exception.Message)" - throw -} \ No newline at end of file diff --git a/src/ScriptCs/Properties/scriptcs.nuspec b/src/ScriptCs/Properties/scriptcs.nuspec index 10aede33..610aa737 100644 --- a/src/ScriptCs/Properties/scriptcs.nuspec +++ b/src/ScriptCs/Properties/scriptcs.nuspec @@ -3,7 +3,7 @@ scriptcs $version$ - Glenn Block, Justin Rusbatch, Filip Wojcieszyn + Glenn Block, Filip Wojcieszyn, Justin Rusbatch, Kristian Hellang, Damian Schenkelman, Adam Ralph Glenn Block, Justin Rusbatch, Filip Wojcieszyn https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md http://scriptcs.net @@ -13,16 +13,12 @@ - - - - - - - - - + + + + + \ No newline at end of file diff --git a/src/ScriptCs/ScriptCs.csproj b/src/ScriptCs/ScriptCs.csproj index 1fadfaea..20e9f2dc 100644 --- a/src/ScriptCs/ScriptCs.csproj +++ b/src/ScriptCs/ScriptCs.csproj @@ -1,148 +1,29 @@ - - - + - {25080671-1A80-4041-B9C7-260578FF4849} + 1.0.0 Exe - ScriptCs + net461 + scriptcs + Glenn Block, Filip Wojcieszyn, Justin Rusbatch + https://github.com/scriptcs/scriptcs/blob/master/LICENSE.md + http://scriptcs.net + http://www.gravatar.com/avatar/5c754f646971d8bc800b9d4057931938.png?s=120 + scriptcs + Write .Net apps with a text editor, NuGet, and the power of Roslyn! + roslyn csx script scriptcs + scriptcs command line tool. scriptcs - ..\..\ - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - ..\..\common\Icon.ico - - - - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.dll - - - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.Configuration.dll - - - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - False - ..\..\packages\PowerArgs.1.6.0.0\lib\net40\PowerArgs.dll - - - False - ..\..\packages\ServiceStack.Text.3.9.47\lib\net35\ServiceStack.Text.dll - - - - - - - - - - - - - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - Guard.cs - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - - - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - Properties\Icon.ico - + - - False - Microsoft .NET Framework 4.5 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - + - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - {e590e710-e159-48e6-a3e6-1a83d3fe732c} - ScriptCs.Core - - - {e79ec231-e27d-4057-91c9-2d001a3a8c3b} - ScriptCs.Engine.Roslyn - - - {9aef2d95-87fb-4829-b384-34bfe076d531} - ScriptCs.Hosting - + + + + - - - \ No newline at end of file diff --git a/src/ScriptCs/ScriptCsArgs.cs b/src/ScriptCs/ScriptCsArgs.cs deleted file mode 100644 index 8e11c88a..00000000 --- a/src/ScriptCs/ScriptCsArgs.cs +++ /dev/null @@ -1,75 +0,0 @@ -using PowerArgs; - -using ScriptCs.Contracts; - -namespace ScriptCs -{ - [ArgExample("scriptcs server.csx -logLevel debug", "Shows how to run the script and display detailed log messages. Useful for debugging.")] - public class ScriptCsArgs - { - public ScriptCsArgs() - { - LogLevel = LogLevel.Info; - Config = "scriptcs.opts"; - } - - [ArgShortcut("repl")] - [ArgDescription("Launch REPL mode when running script. To just launch REPL, simply use 'scriptcs' without any args.")] - public bool Repl { get; set; } - - [ArgPosition(0)] - [ArgShortcut("script")] - [ArgDescription("Script file name, must be specified first")] - public string ScriptName { get; set; } - - [ArgShortcut("?")] - [ArgDescription("Displays help")] - public bool Help { get; set; } - - [ArgShortcut("cache")] - [DefaultValue(false)] - [ArgDescription("Flag which determines whether to run in memory or from a .dll")] - public bool Cache { get; set; } - - [ArgIgnoreCase] - [ArgShortcut("log")] - [DefaultValue(LogLevel.Info)] - [ArgDescription("Flag which defines the log level used.")] - public LogLevel LogLevel { get; set; } - - [ArgShortcut("install")] - [ArgDescription("Installs and restores packages which are specified in packages.config")] - public string Install { get; set; } - - [ArgShortcut("g")] - [ArgDescription("Installs and restores global packages which are specified in packages.config")] - public bool Global { get; set; } - - - [ArgShortcut("save")] - [ArgDescription("Creates a packages.config file based on the packages directory")] - public bool Save { get; set; } - - [ArgShortcut("clean")] - [ArgDescription("Cleans installed packages from working directory")] - public bool Clean { get; set; } - - [ArgShortcut("pre")] - [ArgDescription("Allows installation of packages' prelease versions")] - public bool AllowPreRelease { get; set; } - - [ArgShortcut("version")] - [ArgDescription("Outputs version information")] - public bool Version { get; set; } - - [ArgShortcut("modules")] - [ArgDescription("Specify modules to load")] - public string Modules { get; set; } - - [ArgShortcut("config")] - [DefaultValue("scriptcs.opts")] - [ArgDescription("Defines config file name")] - public string Config { get; set; } - - } -} \ No newline at end of file diff --git a/src/ScriptCs/ScriptServicesBuilderFactory.cs b/src/ScriptCs/ScriptServicesBuilderFactory.cs new file mode 100644 index 00000000..c5450516 --- /dev/null +++ b/src/ScriptCs/ScriptServicesBuilderFactory.cs @@ -0,0 +1,49 @@ +using System.IO; +using ScriptCs.Contracts; +using ScriptCs.Hosting; + +namespace ScriptCs +{ + public static class ScriptServicesBuilderFactory + { + public static IScriptServicesBuilder Create(Config config, string[] scriptArgs) + { + Guard.AgainstNullArgument("commandArgs", config); + Guard.AgainstNullArgument("scriptArgs", scriptArgs); + + IConsole console = new ScriptConsole(); + if (!string.IsNullOrWhiteSpace(config.OutputFile)) + { + console = new FileConsole(config.OutputFile, console); + } + + var logProvider = new ColoredConsoleLogProvider(config.LogLevel, console); + var initializationServices = new InitializationServices(logProvider); + initializationServices.GetAppDomainAssemblyResolver().Initialize(); + + if (config.ScriptName != null && Path.GetFileName(config.ScriptName) != config.ScriptName) + { + var path = Path.GetFullPath(config.ScriptName); + initializationServices.GetFileSystem().CurrentDirectory = Path.GetDirectoryName(path); + config.ScriptName = path; + } + + // NOTE (adamralph): this is a hideous assumption about what happens inside the CommandFactory. + // It is a result of the ScriptServicesBuilderFactory also having to know what is going to happen inside the + // Command Factory so that it builds the builder(:-p) correctly in advance. + // This demonstrates the technical debt that exists with the ScriptServicesBuilderFactory and CommandFactory + // in their current form. We have a separate refactoring task raised to address this. + var repl = config.Repl || + (!config.Clean && config.PackageName == null && !config.Save && config.ScriptName == null); + + var scriptServicesBuilder = new ScriptServicesBuilder(console, logProvider, null, initializationServices) + .Cache(config.Cache) + .Debug(config.Debug) + .LogLevel(config.LogLevel) + .ScriptName(config.ScriptName) + .Repl(repl); + + return scriptServicesBuilder.LoadModules(Path.GetExtension(config.ScriptName) ?? ".csx", config.Modules); + } + } +} diff --git a/src/ScriptCs/VersionWriter.cs b/src/ScriptCs/VersionWriter.cs new file mode 100644 index 00000000..e1bf4b36 --- /dev/null +++ b/src/ScriptCs/VersionWriter.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; + +namespace ScriptCs +{ + public static class VersionWriter + { + private static readonly string version = + FileVersionInfo.GetVersionInfo(typeof(VersionWriter).Assembly.Location).ProductVersion; + + private static readonly Regex colorRegex = new Regex( + @"\+(?\w*)(?(.*(?=\+))|.*)", RegexOptions.Compiled | RegexOptions.Singleline); + + public static void Write() + { + var lines = new[] + { + @"+cyan _ _", + @"+cyan ___ ___ _ __(_)_ __ | |__+darkMagenta ___ ___", + @"+cyan/ __|/ __| '__| | '_ \| __/+darkMagenta/ __/ __|", + @"+cyan\__ \ (__| | | | |_) | |_+darkMagenta| (__\__ \", + @"+cyan|___/\___|_| |_| .__/ \__\+darkMagenta\___|___/", + string.Format(@"+cyan |_|+white Version: {0}", version) + }; + + foreach (var lineMatches in lines.Select(line => colorRegex.Matches(line))) + { + foreach (Match match in lineMatches) + { + ConsoleColor color; + if (Enum.TryParse(match.Groups["color"].Value, true, out color)) + { + Console.ForegroundColor = color; + } + + try + { + Console.Write(match.Groups["ascii"].Value); + } + finally + { + Console.ResetColor(); + } + } + + Console.WriteLine(); + } + + Console.WriteLine(); + } + } +} diff --git a/src/ScriptCs/app.config b/src/ScriptCs/app.config index cb1b9770..a61f4a50 100644 --- a/src/ScriptCs/app.config +++ b/src/ScriptCs/app.config @@ -1,14 +1,10 @@  - + - - - - - \ No newline at end of file + diff --git a/src/ScriptCs/packages.config b/src/ScriptCs/packages.config deleted file mode 100644 index 65fd6cca..00000000 --- a/src/ScriptCs/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/ScriptCs/scriptcs b/src/ScriptCs/scriptcs new file mode 100755 index 00000000..5f56d178 --- /dev/null +++ b/src/ScriptCs/scriptcs @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +## Make sure to chmod +x this file! + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +mono "$DIR/scriptcs.exe" $@ \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/AppDomainAssemblyResolverTests.cs b/test/ScriptCs.Core.Tests/AppDomainAssemblyResolverTests.cs new file mode 100644 index 00000000..a20b191d --- /dev/null +++ b/test/ScriptCs.Core.Tests/AppDomainAssemblyResolverTests.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Moq; +using ScriptCs.Contracts; +using Should; +using Xunit; +using Xunit.Extensions; +using AutoFixture.Xunit2; +using System.Threading; + +namespace ScriptCs.Tests +{ + public class AppDomainAssemblyResolverTests + { + static AppDomainAssemblyResolverTests() + { + _assemblyName = typeof(Mock).Assembly.GetName(); + _info = new AssemblyInfo { Path = _assemblyName.CodeBase.Substring(8).Replace('/', Path.DirectorySeparatorChar) }; + } + + private static AssemblyName _assemblyName; + private static AssemblyInfo _info; + + public class TheConstructor + { + [Theory(Skip = "AppDomain events are flaky here - needs to be reviewed"), ScriptCsAutoData] + public void ShouldSubscribeToTheResolveEvent( + TestLogProvider logProvider, + IFileSystem fileSystem, + IAssemblyResolver assemblyResolver, + [Frozen] Mock assemblyUtilityMock) + { + var autoResetEvent = new AutoResetEvent(false); + + assemblyUtilityMock.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); + var assemblyUtility = assemblyUtilityMock.Object; + + new AppDomainAssemblyResolver(logProvider, fileSystem, assemblyResolver, + assemblyUtility, + resolveHandler: (o, r) => + { + autoResetEvent.Set(); + return Assembly.GetExecutingAssembly(); + } + ); + Assembly.Load("test"); + var called = autoResetEvent.WaitOne(1000); + called.ShouldBeTrue(); + } + } + + public class TheInitializeMethod + { + [Theory, ScriptCsAutoData] + public void ShouldAddHostAssemblyPaths( + [Frozen] Mock fileSystemMock, + Mock resolverMock) + { + var hostBin = "c:\test"; + var dll = "c:\test\test.dll"; + + fileSystemMock.Setup(fs => fs.EnumerateFiles(hostBin, "*.dll", SearchOption.TopDirectoryOnly)).Returns(new[] { dll }); + fileSystemMock.SetupGet(fs => fs.HostBin).Returns(hostBin); + resolverMock.Setup(r => r.AddAssemblyPaths(It.IsAny>())); + resolverMock.Object.Initialize(); + resolverMock.Verify(r => r.AddAssemblyPaths(It.Is>(paths => paths.Contains(dll)))); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddModuleAssemblyPaths( + [Frozen] Mock fileSystemMock, + [Frozen] Mock assemblyResolverMock, + Mock resolverMock) + { + var modulesFolder = "c:\test"; + var dll = "c:\test\test.dll"; + assemblyResolverMock.Setup(a => a.GetAssemblyPaths(modulesFolder, true)).Returns(new[] { dll }); + fileSystemMock.SetupGet(fs => fs.GlobalFolder).Returns(modulesFolder); + resolverMock.Setup(r => r.AddAssemblyPaths(It.IsAny>())); + resolverMock.Object.Initialize(); + resolverMock.Verify(r => r.AddAssemblyPaths(It.Is>(paths => paths.Contains(dll)))); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddScriptPackAssemblyPaths( + [Frozen] Mock fileSystemMock, + [Frozen] Mock assemblyResolverMock, + Mock resolverMock) + { + var scriptAssemblyPath = "c:\test"; + var dll = "c:\test\test.dll"; + assemblyResolverMock.Setup(a => a.GetAssemblyPaths(scriptAssemblyPath, true)).Returns(new[] { dll }); + fileSystemMock.SetupGet(fs => fs.CurrentDirectory).Returns(scriptAssemblyPath); + resolverMock.Setup(r => r.AddAssemblyPaths(It.IsAny>())); + resolverMock.Object.Initialize(); + resolverMock.Verify(r => r.AddAssemblyPaths(It.Is>(paths => paths.Contains(dll)))); + } + } + + public class TheAddAssemblyPathsMethod + { + //I can't use Autofixture's [Frozen] attribute here as there appears to be a bug. If you try to freeze a Mock> + //instead of a configurable Mock, an Dictionary<> is injected. I need to override TryGetValue for this test. + [Fact] + public void ShouldRetrieveTheMappedAssemblyInfo() + { + var assemblyUtilityMock = new Mock(); + var assemblyInfoMapMock = new Mock>(); + assemblyUtilityMock.Setup(u => u.IsManagedAssembly(It.IsAny())).Returns(true); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + AssemblyInfo foundInfo = null; + assemblyInfoMapMock.Setup(m => m.TryGetValue(It.IsAny(), out foundInfo)).Returns(false); + + var resolver = new AppDomainAssemblyResolver( + new TestLogProvider(), + Mock.Of(), + Mock.Of(), + assemblyUtilityMock.Object, + assemblyInfoMapMock.Object + ); + + resolver.AddAssemblyPaths(new[] { _info.Path }); + assemblyInfoMapMock.Verify(m => m.TryGetValue(_assemblyName.Name, out foundInfo)); + } + + //Here I can use [Frozen] as I don't need to override members + [Theory, ScriptCsAutoData] + public void ShouldRegisterTheAssemblyIfTheAssemblyDoesNotExist( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + assemblyUtilityMock.Setup(u => u.IsManagedAssembly(It.IsAny())).Returns(true); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + resolver.AddAssemblyPaths(new[] { _info.Path }); + assemblyInfoMap.ContainsKey(_assemblyName.Name).ShouldBeTrue(); + } + + [Theory, ScriptCsAutoData] + public void ShouldOverrideIfTheAssemblyVersionIsGreaterThanTheMappedAssemblyAndItWasNotLoaded( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + assemblyUtilityMock.Setup(u => u.IsManagedAssembly(It.IsAny())).Returns(true); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + _info.Version = new Version(0, 0); + resolver.AddAssemblyPaths(new[] { _info.Path }); + _info = assemblyInfoMap[_assemblyName.Name]; + _info.Version.ShouldEqual(_assemblyName.Version); + } + + [Theory, ScriptCsAutoData] + public void ShouldLogWhenTheAssemblyIsMapped( + [Frozen] Mock assemblyUtilityMock, + [Frozen] TestLogProvider logProvider, + AppDomainAssemblyResolver resolver) + { + assemblyUtilityMock.Setup(u => u.IsManagedAssembly(It.IsAny())).Returns(true); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + resolver.AddAssemblyPaths(new[] { _info.Path }); + logProvider.Output.ShouldContain( + "DEBUG: Mapping Assembly " + _assemblyName.Name + " to version:" + _assemblyName.Version); + } + + [Theory, ScriptCsAutoData] + public void ShouldWarnIfTheAssemblyVersionIsGreaterThanTheMappedAssemblyAndItWasLoaded( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + [Frozen] TestLogProvider logProvider, + AppDomainAssemblyResolver resolver) + { + _info.Version = new Version(0, 0); + assemblyUtilityMock.Setup(u => u.IsManagedAssembly(It.IsAny())).Returns(true); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + _info.Assembly = typeof(Mock).Assembly; + assemblyInfoMap[_assemblyName.Name] = _info; + resolver.AddAssemblyPaths(new[] { _info.Path }); + logProvider.Output.ShouldContain( + "WARN: Conflict: Assembly " + _info.Path + " with version " + _assemblyName.Version + + " cannot be added as it has already been resolved"); + } + + [Theory, ScriptCsAutoData] + public void ShouldNotOverrideIfTheAssemblyVersionIsGreaterThanTheMappedAssemblyAndItWasLoaded( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + _info.Version = new Version(0, 0); + _info.Assembly = typeof(Mock).Assembly; + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + assemblyInfoMap[_assemblyName.Name] = _info; + resolver.AddAssemblyPaths(new[] { _info.Path }); + var newInfo = assemblyInfoMap[_assemblyName.Name]; + newInfo.Version.ShouldEqual(_info.Version); + } + + [Theory, ScriptCsAutoData] + public void ShouldNotOverrideIfTheAssemblyVersionIsLessThenOrEqualToTheMappedAssembly( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + _info.Version = new Version(99, 0, 0); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + assemblyInfoMap[_assemblyName.Name] = _info; + resolver.AddAssemblyPaths(new[] { _info.Path }); + var newInfo = assemblyInfoMap[_assemblyName.Name]; + newInfo.Version.ShouldEqual(_info.Version); + } + } + + public class TheAssemblyResolveMethod + { + [Fact] + public void ShouldRetrieveTheMappedAssemblyInfo() + { + var assemblyUtilityMock = new Mock(); + var assemblyInfoMapMock = new Mock>(); + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + AssemblyInfo foundInfo = null; + assemblyInfoMapMock.Setup(m => m.TryGetValue(It.IsAny(), out foundInfo)).Returns(false); + + var resolver = new AppDomainAssemblyResolver( + new TestLogProvider(), + Mock.Of(), + Mock.Of(), + assemblyUtilityMock.Object, + assemblyInfoMapMock.Object + ); + + resolver.AssemblyResolve(this, new ResolveEventArgs(_assemblyName.Name)); + assemblyInfoMapMock.Verify(m => m.TryGetValue(_assemblyName.Name, out foundInfo)); + } + + [Theory, ScriptCsAutoData] + public void ShouldLoadTheAssemblyIfTheMappedAssemblyInfoExistsAndItHasNotBeenLoaded( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + assemblyUtilityMock.Setup(u => u.LoadFile(_info.Path)).Returns(typeof(Mock).Assembly); + assemblyInfoMap[_assemblyName.Name] = _info; + var args = new ResolveEventArgs(_assemblyName.Name); + var assembly = resolver.AssemblyResolve(this, args); + assembly.ShouldEqual(_info.Assembly); + } + + [Theory, ScriptCsAutoData] + public void ShouldLogTheAssemblyThatIsBeingResolved( + [Frozen] Mock assemblyUtilityMock, + [Frozen] TestLogProvider logProvider, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + assemblyUtilityMock.Setup(u => u.LoadFile(_info.Path)).Returns(typeof(Mock).Assembly); + assemblyInfoMap[_assemblyName.Name] = _info; + + resolver.AssemblyResolve(this, new ResolveEventArgs(_assemblyName.Name)); + logProvider.Output.ShouldContain( + "DEBUG: Resolving from: " + _assemblyName.Name + " to: " + _assemblyName.ToString()); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnTheAssemblyIfItWasLoaded( + [Frozen] Mock assemblyUtilityMock, + [Frozen] IDictionary assemblyInfoMap, + AppDomainAssemblyResolver resolver) + { + assemblyUtilityMock.Setup(u => u.GetAssemblyName(_info.Path)).Returns(_assemblyName); + assemblyUtilityMock.Setup(u => u.LoadFile(_info.Path)).Returns(typeof(Mock).Assembly); + assemblyInfoMap[_assemblyName.Name] = _info; + var args = new ResolveEventArgs(_assemblyName.Name); + var assembly = resolver.AssemblyResolve(this, args); + assembly.ShouldEqual(_info.Assembly); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/AssemblyResolverTests.cs b/test/ScriptCs.Core.Tests/AssemblyResolverTests.cs index 0e94caee..29addbe5 100644 --- a/test/ScriptCs.Core.Tests/AssemblyResolverTests.cs +++ b/test/ScriptCs.Core.Tests/AssemblyResolverTests.cs @@ -1,16 +1,10 @@ -using System; -using System.IO; +using System.IO; using System.Linq; - -using Common.Logging; - using Moq; - using ScriptCs.Contracts; - using Should; - using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -18,21 +12,25 @@ public class AssemblyResolverTests { public class GetAssemblyPathsMethod { - [Fact] - public void ShouldReturnAssembliesFromPackagesFolder() + [Theory, ScriptCsAutoData] + public void ShouldReturnAssembliesFromPackagesFolder( + [Frozen] Mock fileSystemMock, + [Frozen] Mock packageAssemblyResolverMock, + [Frozen] Mock assemblyUtilityMock, + AssemblyResolver resolver + ) { const string WorkingDirectory = @"C:\"; - var packagesFolder = Path.Combine(WorkingDirectory, Constants.PackagesFolder); + var packagesFolder = Path.Combine(WorkingDirectory, "packages"); var assemblyFile = Path.Combine(packagesFolder, "MyAssembly.dll"); - var fileSystem = new Mock(); - fileSystem.Setup(x => x.DirectoryExists(packagesFolder)).Returns(true); - - var packageAssemblyResolver = new Mock(); - packageAssemblyResolver.Setup(x => x.GetAssemblyNames(WorkingDirectory)).Returns(new[] { assemblyFile }); + assemblyUtilityMock.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); + fileSystemMock.Setup(x => x.DirectoryExists(packagesFolder)).Returns(true); + fileSystemMock.SetupGet(x => x.PackagesFolder).Returns("packages"); + fileSystemMock.SetupGet(x => x.BinFolder).Returns("bin"); - var resolver = new AssemblyResolver(fileSystem.Object, packageAssemblyResolver.Object, Mock.Of(), Mock.Of()); + packageAssemblyResolverMock.Setup(x => x.GetAssemblyNames(WorkingDirectory)).Returns(new[] { assemblyFile }); var assemblies = resolver.GetAssemblyPaths(WorkingDirectory).ToList(); @@ -40,22 +38,24 @@ public void ShouldReturnAssembliesFromPackagesFolder() assemblies[0].ShouldEqual(assemblyFile); } - [Fact] - public void ShouldReturnAssembliesFromBinFolder() + [Theory, ScriptCsAutoData] + public void ShouldReturnAssembliesFromBinFolder( + [Frozen] Mock fileSystemMock, + [Frozen] Mock assemblyUtilityMock, + AssemblyResolver resolver + ) { const string WorkingDirectory = @"C:\"; - var binFolder = Path.Combine(WorkingDirectory, Constants.BinFolder); + var binFolder = Path.Combine(WorkingDirectory, "bin"); var assemblyFile = Path.Combine(binFolder, "MyAssembly.dll"); - var fileSystem = new Mock(); - fileSystem.Setup(x => x.DirectoryExists(binFolder)).Returns(true); - fileSystem.Setup(x => x.EnumerateFiles(binFolder, It.IsAny(), SearchOption.AllDirectories)).Returns(new[] { assemblyFile }); + fileSystemMock.Setup(x => x.DirectoryExists(binFolder)).Returns(true); + fileSystemMock.SetupGet(x => x.PackagesFolder).Returns("packages"); + fileSystemMock.SetupGet(x => x.BinFolder).Returns("bin"); + fileSystemMock.Setup(x => x.EnumerateFiles(binFolder, It.IsAny(), SearchOption.TopDirectoryOnly)).Returns(new[] { assemblyFile }); - var assemblyUtility = new Mock(); - assemblyUtility.Setup(x => x.IsManagedAssembly(assemblyFile)).Returns(true); - - var resolver = new AssemblyResolver(fileSystem.Object, Mock.Of(), assemblyUtility.Object, Mock.Of()); + assemblyUtilityMock.Setup(x => x.IsManagedAssembly(assemblyFile)).Returns(true); var assemblies = resolver.GetAssemblyPaths(WorkingDirectory).ToList(); @@ -63,31 +63,61 @@ public void ShouldReturnAssembliesFromBinFolder() assemblies[0].ShouldEqual(assemblyFile); } - [Fact] - public void ShouldNotReturnNonManagedAssemblies() + [Theory, ScriptCsAutoData] + public void ShouldNotReturnNonManagedAssemblies( + [Frozen] Mock fileSystemMock, + [Frozen] Mock assemblyUtilityMock, + AssemblyResolver resolver + ) { const string WorkingDirectory = @"C:\"; - var binFolder = Path.Combine(WorkingDirectory, Constants.BinFolder); + var binFolder = Path.Combine(WorkingDirectory, "bin"); var managed = Path.Combine(binFolder, "MyAssembly.dll"); var nonManaged = Path.Combine(binFolder, "MyNonManagedAssembly.dll"); - var fileSystem = new Mock(); - fileSystem.Setup(x => x.DirectoryExists(binFolder)).Returns(true); - fileSystem.Setup(x => x.EnumerateFiles(binFolder, It.IsAny(), SearchOption.AllDirectories)) + fileSystemMock.Setup(x => x.DirectoryExists(binFolder)).Returns(true); + fileSystemMock.SetupGet(x => x.PackagesFolder).Returns("packages"); + fileSystemMock.SetupGet(x => x.BinFolder).Returns("bin"); + fileSystemMock.Setup(x => x.EnumerateFiles(binFolder, It.IsAny(), SearchOption.TopDirectoryOnly + )) .Returns(new[] { managed, nonManaged }); - var assemblyUtility = new Mock(); - assemblyUtility.Setup(x => x.IsManagedAssembly(managed)).Returns(true); - assemblyUtility.Setup(x => x.IsManagedAssembly(nonManaged)).Returns(false); - - var resolver = new AssemblyResolver(fileSystem.Object, Mock.Of(), assemblyUtility.Object, Mock.Of()); + assemblyUtilityMock.Setup(x => x.IsManagedAssembly(managed)).Returns(true); + assemblyUtilityMock.Setup(x => x.IsManagedAssembly(nonManaged)).Returns(false); var assemblies = resolver.GetAssemblyPaths(WorkingDirectory).ToList(); assemblies.Count.ShouldEqual(1); assemblies[0].ShouldEqual(managed); } + + [Theory, ScriptCsAutoData] + public void ShouldOnlyReturnBinariesWhenFlagIsSet( + [Frozen] Mock packageAssemblyResolverMock, + [Frozen] Mock fileSystemMock, + [Frozen] Mock assemblyUtilityMock, + AssemblyResolver resolver) + { + const string WorkingDirectory = @"C:\"; + + var binFolder = Path.Combine(WorkingDirectory, "bin"); + + assemblyUtilityMock.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); + fileSystemMock.Setup(x => x.DirectoryExists(binFolder)).Returns(true); + fileSystemMock.Setup(x => x.DirectoryExists(@"C:\packages")).Returns(true); + fileSystemMock.SetupGet(x => x.PackagesFolder).Returns("packages"); + fileSystemMock.SetupGet(x => x.BinFolder).Returns("bin"); + fileSystemMock.Setup(x => x.EnumerateFiles(binFolder, It.IsAny(), SearchOption.AllDirectories)) + .Returns(Enumerable.Empty()); + + packageAssemblyResolverMock.Setup(p=>p.GetAssemblyNames(WorkingDirectory)).Returns(new string[] {"test.dll", "test.exe", "test.foo"}); + + var assemblies = resolver.GetAssemblyPaths(WorkingDirectory, true).ToList(); + assemblies.ShouldNotContain("test.foo"); + assemblies.ShouldContain("test.dll"); + assemblies.ShouldContain("test.exe"); + } } } } \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/AssemblyUtilityTests.cs b/test/ScriptCs.Core.Tests/AssemblyUtilityTests.cs new file mode 100644 index 00000000..4e1a8834 --- /dev/null +++ b/test/ScriptCs.Core.Tests/AssemblyUtilityTests.cs @@ -0,0 +1,27 @@ +using System; +using ScriptCs.Contracts; +using Should; +using Xunit; + +namespace ScriptCs.Tests +{ + public class AssemblyUtilityTests + { + public class TheIsManagedAssemblyMethod + { + private readonly IAssemblyUtility _assemblyUtility = new AssemblyUtility(); + + [Fact] + public void ShouldReturnFalseWhenThePathDoesNotPointToAManagedAssembly() + { + _assemblyUtility.IsManagedAssembly("ScriptCs.Core.Tests.dll.config").ShouldBeFalse(); + } + + [Fact] + public void ShouldReturnTrueWhenThePathPointsToAManagedAssembly() + { + _assemblyUtility.IsManagedAssembly(typeof(String).Assembly.Location).ShouldBeTrue(); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/DirectiveLineProcessorTests.cs b/test/ScriptCs.Core.Tests/DirectiveLineProcessorTests.cs index 1c634ef4..c95f84d0 100644 --- a/test/ScriptCs.Core.Tests/DirectiveLineProcessorTests.cs +++ b/test/ScriptCs.Core.Tests/DirectiveLineProcessorTests.cs @@ -1,12 +1,5 @@ -using Moq; -using ScriptCs.Contracts; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using ScriptCs.Contracts; using Should; -using Moq.Protected; using Xunit; namespace ScriptCs.Tests @@ -34,6 +27,48 @@ public void ShouldReturnTrueAndContinueProcessingDirectiveIfAfterCodeAndBehaviou } } + public class TheMatchesMethod + { + [Fact] + public void ShouldReturnTrueWhenLineMatchesDirectiveStringWithAnArgument() + { + var directiveLineProcessor = new TestableDirectiveLineProcessor(); + directiveLineProcessor.Matches("#Test argument").ShouldBeTrue(); + } + + [Fact] + public void ShouldReturnTrueWhenLineMatchesDirectiveStringWithoutAnArgument() + { + var directiveLineProcessor = new TestableDirectiveLineProcessor(); + directiveLineProcessor.Matches("#Test").ShouldBeTrue(); + } + + [Fact] + public void ShouldReturnFalseWhenLineDoesNotMatchDirectiveStringWithAnArgument() + { + var directiveLineProcessor = new TestableDirectiveLineProcessor(); + directiveLineProcessor.Matches("#NotATest argument").ShouldBeFalse(); + } + + [Fact] + public void ShouldReturnFalseWhenLineDoesNotMatchDirectiveStringWithoutAnArgument() + { + var directiveLineProcessor = new TestableDirectiveLineProcessor(); + directiveLineProcessor.Matches("#NotATest").ShouldBeFalse(); + } + } + + public class TheGetDirectiveArgumentMethod + { + [Fact] + public void ShouldParseTheArgumentFromTheDirectiveLine() + { + var directiveLineProcessor = new TestableDirectiveLineProcessor(BehaviorAfterCode.Allow); + directiveLineProcessor.ProcessLine(null, null, "#Test argument", false); + directiveLineProcessor.ArgumentParsedCorrectly.ShouldBeTrue(); + } + } + public class TestableDirectiveLineProcessor : DirectiveLineProcessor { private BehaviorAfterCode? _behaviourAfterCode; @@ -54,9 +89,11 @@ protected override string DirectiveName { get { return "Test"; } } + public bool ArgumentParsedCorrectly { get; private set; } public bool InheritedProcessLineCalled { get; private set; } protected override bool ProcessLine(IFileParser parser, FileParserContext context, string line) { + ArgumentParsedCorrectly = GetDirectiveArgument(line) == "argument"; InheritedProcessLineCalled = true; return true; } diff --git a/test/ScriptCs.Core.Tests/FileProcessorTests.cs b/test/ScriptCs.Core.Tests/FileProcessorTests.cs index fbd5b28d..179a849d 100644 --- a/test/ScriptCs.Core.Tests/FileProcessorTests.cs +++ b/test/ScriptCs.Core.Tests/FileProcessorTests.cs @@ -1,23 +1,67 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; -using Common.Logging; using Moq; - using ScriptCs.Contracts; - +using ScriptCs.Contracts.Exceptions; using Should; using Xunit; -using ScriptCs.Contracts.Exceptions; namespace ScriptCs.Tests { public class FileProcessorTests { + public class TheParseFileMethod + { + private readonly Mock _fileSystem; + + public TheParseFileMethod() + { + _fileSystem = new Mock(); + _fileSystem.SetupGet(x => x.NewLine).Returns(Environment.NewLine); + _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == @"c:\test\main.csx"))) + .Returns(new string[] {"main"}); + _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == @"c:\test\child.csx"))) + .Returns(new string[] {"child"}); + + _fileSystem.Setup(fs => fs.GetFullPath(It.IsAny())).Returns((path) => path); + } + + [Fact] + public void SetsTheScriptPath() + { + var path = @"c:\test\main.csx"; + var processor = GetFilePreProcessor(); + var context = new FileParserContext(); + processor.ParseFile(path, context); + _fileSystem.Verify(x => x.ReadFileLines(It.Is(f => f == path)), Times.Exactly(1)); + context.ScriptPath.ShouldEqual(path); + } + + [Fact] + public void AddsLoadedScripts() + { + var path = @"c:\test\child.csx"; + var processor = GetFilePreProcessor(); + var context = new FileParserContext(); + context.ScriptPath = @"c:\test\main.csx"; + processor.ParseFile(path, context); + _fileSystem.Verify(x => x.ReadFileLines(It.Is(f => f == path)), Times.Exactly(1)); + context.LoadedScripts.ShouldContain(path); + } + + private IFilePreProcessor GetFilePreProcessor() + { + return new FilePreProcessor(_fileSystem.Object, new TestLogProvider(), Enumerable.Empty()); + } + } + public class ProcessFileMethod { private List _file1 = new List { + @"#!/usr/bin/env scriptcs", @"#load ""script2.csx""", @"#load ""script4.csx"";", "using System;", @@ -52,16 +96,6 @@ public class ProcessFileMethod @"Console.WriteLine(""Goodbye Script 4"");" }; - private List _file5 = new List - { - "using System;", - @"Console.WriteLine(""Hello Script 2"");", - @"Console.WriteLine(""Loading Script 3"");", - @"#load ""script3.csx""", - @"Console.WriteLine(""Loaded Script 3"");", - @"Console.WriteLine(""Goodbye Script 2"");" - }; - private readonly Mock _fileSystem; public ProcessFileMethod() @@ -86,14 +120,14 @@ public void MultipleUsingStatementsShouldProduceDistinctOutput() var processor = GetFilePreProcessor(); var result = processor.ProcessFile("script1.csx"); - var splitOutput = result.Code.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + result.Code.Split(new[] { Environment.NewLine }, StringSplitOptions.None); _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i.StartsWith("script"))), Times.Exactly(3)); - Assert.Equal(2, splitOutput.Count(x => x.TrimStart(' ').StartsWith("using "))); + result.Namespaces.Count.ShouldEqual(2); } [Fact] - public void UsingStateMentsShoulAllBeAtTheTop() + public void UsingStateMentsShouldAllBeAtTheTop() { var processor = GetFilePreProcessor(); var result = processor.ProcessFile("script1.csx"); @@ -123,8 +157,7 @@ public void ShouldReturnResultWithAllLoadedFiles() var processor = GetFilePreProcessor(); var result = processor.ProcessFile("script1.csx"); - result.LoadedScripts.Count.ShouldEqual(3); - result.LoadedScripts.ShouldContain("script1.csx"); + result.LoadedScripts.Count.ShouldEqual(2); result.LoadedScripts.ShouldContain("script2.csx"); result.LoadedScripts.ShouldContain("script4.csx"); } @@ -196,6 +229,24 @@ public void ShouldNotIncludeReferencesInCode() result.Code.ShouldNotContain("#r"); } + [Fact] + public void ShouldNotIncludeShebangsInCode() + { + var file1 = new List + { + @"#!/usr/bin/env scriptcs", + "using System;", + @"Console.WriteLine(""Hi!"");" + }; + + _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "script1.csx"))).Returns(file1.ToArray()); + + var processor = GetFilePreProcessor(); + var result = processor.ProcessFile("script1.csx"); + + result.Code.ShouldNotContain("#!/usr/bin/env"); + } + [Fact] public void ShouldNotLoadSameFileTwice() { @@ -242,7 +293,8 @@ public void LoadBeforeUsingShouldBeAllowed() var lastUsing = splitOutput.ToList().FindLastIndex(x => x.TrimStart(' ').StartsWith("using ")); var firsNotUsing = splitOutput.ToList().FindIndex(x => !x.TrimStart(' ').StartsWith("using ")); - splitOutput.Count(x => x.TrimStart(' ').StartsWith("using ")).ShouldEqual(2); + splitOutput.Count(x => x.TrimStart(' ').StartsWith("using ")).ShouldEqual(0); + result.Namespaces.ShouldContain("System"); Assert.True(lastUsing < firsNotUsing); } @@ -255,7 +307,8 @@ public void ShouldNotThrowStackOverflowExceptionOnLoadLoop() _fileSystem.Setup(x => x.ReadFileLines("A.csx")).Returns(a.ToArray()); _fileSystem.Setup(x => x.ReadFileLines("B.csx")).Returns(b.ToArray()); - Assert.DoesNotThrow(() => GetFilePreProcessor().ProcessFile("A.csx")); + var ex = Record.Exception(() => GetFilePreProcessor().ProcessFile("A.csx")); + Assert.Null(ex); } [Fact] @@ -271,7 +324,7 @@ public void ShouldNotBeAllowedToLoadAfterUsing() _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "file.csx"))).Returns(file.ToArray()); var processor = GetFilePreProcessor(); - Assert.Throws(typeof(InvalidDirectiveUseException), () => processor.ProcessFile("file.csx")); + Assert.Throws(() => processor.ProcessFile("file.csx")); _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "script4.csx")), Times.Never()); } @@ -393,12 +446,8 @@ public void ShouldAddLineDirectiveRightAfterLastLoadIsIncludedInEachFile() var fileLines = result.Code.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - // using statements go first, after that f4 -> f5 -> f2 -> f3 -> f1 + // using should not be here, so only f4 -> f5 -> f2 -> f3 -> f1 var line = 0; - fileLines[line++].ShouldEqual("using System;"); - fileLines[line++].ShouldEqual("using System.Diagnostics;"); - - line++; // Skip blank separator line between usings and body... fileLines[line++].ShouldEqual(@"#line 1 ""C:\f4.csx"""); fileLines[line++].ShouldEqual(f4[0]); @@ -414,6 +463,8 @@ public void ShouldAddLineDirectiveRightAfterLastLoadIsIncludedInEachFile() fileLines[line++].ShouldEqual(@"#line 5 ""C:\f1.csx"""); fileLines[line].ShouldEqual(f1[4]); + + result.Namespaces.Count.ShouldEqual(2); } [Fact] @@ -469,7 +520,7 @@ public void ShouldLoadNestedScriptcsRelativeToScriptLocation() var preProcessor = GetFilePreProcessor(); - var result = preProcessor.ProcessFile(@"C:\f1.csx"); + preProcessor.ProcessFile(@"C:\f1.csx"); _fileSystem.Verify(fs => fs.ReadFileLines(@"C:\f1.csx"), Times.Once()); _fileSystem.Verify(fs => fs.ReadFileLines(@"C:\SubFolder\f2.csx"), Times.Once()); @@ -504,7 +555,7 @@ public void ShouldResetTheCurrentDirectoryWhenLoadingScript() var preProcessor = GetFilePreProcessor(); - var result = preProcessor.ProcessFile(@"C:\f1.csx"); + preProcessor.ProcessFile(@"C:\f1.csx"); lastCurrentDirectory.ShouldBeSameAs(startingDirectory); } @@ -515,13 +566,14 @@ private IFilePreProcessor GetFilePreProcessor() { new UsingLineProcessor(), new ReferenceLineProcessor(_fileSystem.Object), - new LoadLineProcessor(_fileSystem.Object) + new LoadLineProcessor(_fileSystem.Object), + new ShebangLineProcessor() }; - return new FilePreProcessor(_fileSystem.Object, Mock.Of(), lineProcessors); + return new FilePreProcessor(_fileSystem.Object, new TestLogProvider(), lineProcessors); } } - + public class ProcessScriptMethod { private readonly Mock _fileSystem; @@ -538,7 +590,7 @@ public void ShouldSplitScriptIntoLines() { var preProcessor = GetFilePreProcessor(); var script = @"Console.WriteLine(""Testing..."");"; - + preProcessor.ProcessScript(script); _fileSystem.Verify(x => x.SplitLines(script), Times.Once()); @@ -549,7 +601,7 @@ public void ShouldNotReadFromFile() { var preProcessor = GetFilePreProcessor(); var script = @"Console.WriteLine(""Testing..."");"; - + preProcessor.ProcessScript(script); _fileSystem.Verify(x => x.ReadFileLines(It.IsAny()), Times.Never()); @@ -565,7 +617,7 @@ public void ShouldNotIncludeLineDirectiveForRootScript() var preProcessor = GetFilePreProcessor(); var script = @"#load script1.csx"; - + var result = preProcessor.ProcessScript(script); var fileLines = result.Code.Split(new[] { Environment.NewLine }, StringSplitOptions.None); @@ -578,10 +630,98 @@ private IFilePreProcessor GetFilePreProcessor() { new UsingLineProcessor(), new ReferenceLineProcessor(_fileSystem.Object), - new LoadLineProcessor(_fileSystem.Object) + new LoadLineProcessor(_fileSystem.Object), + new ShebangLineProcessor() }; - return new FilePreProcessor(_fileSystem.Object, Mock.Of(), lineProcessors); + return new FilePreProcessor(_fileSystem.Object, new TestLogProvider(), lineProcessors); + } + } + + public class TheParseScriptMethod + { + private readonly Mock _fileSystem; + + public TheParseScriptMethod() + { + _fileSystem = new Mock(); + _fileSystem.SetupGet(x => x.NewLine).Returns(Environment.NewLine); + _fileSystem.Setup(fs => fs.GetFullPath(It.IsAny())).Returns((path) => path); + } + + [Fact] + public void ShouldAllowSingleLineCommentsBeforeDirectives() + { + var testableDirectiveProcessor = new DirectiveLineProcessorTests.TestableDirectiveLineProcessor(BehaviorAfterCode.Throw); + var filePreprocessor = GetFilePreProcessor(testableDirectiveProcessor, new LoadLineProcessor(_fileSystem.Object)); + var lines = new List + { + "//Test Comment", + "#Test something", + "Console.WriteLine(\"Success\");" + }; + + var ex = Record.Exception(() => filePreprocessor.ParseScript(lines, new FileParserContext())); + Assert.Null(ex); + } + + [Fact] + public void ShouldProcessCustomDirectiveWhenItComesBeforeCode() + { + var testableDirectiveProcessor = new DirectiveLineProcessorTests.TestableDirectiveLineProcessor(); + var filePreprocessor = GetFilePreProcessor(testableDirectiveProcessor, new LoadLineProcessor(_fileSystem.Object)); + var lines = new List + { + "#Test something", + "Console.WriteLine(\"Success\");" + }; + + filePreprocessor.ParseScript(lines, new FileParserContext()); + testableDirectiveProcessor.InheritedProcessLineCalled.ShouldBeTrue(); + } + + [Fact] + public void ShouldProcessALoadDirectiveWhenItComesAfterACustomDirective() + { + var testableDirectiveProcessor = new DirectiveLineProcessorTests.TestableDirectiveLineProcessor(); + var loadLineProcessor = new TestableLoadLineProcessor(_fileSystem.Object); + var filePreprocessor = GetFilePreProcessor(testableDirectiveProcessor, loadLineProcessor); + var lines = new List + { + "#Test something", + "#load myscript.csx", + "Console.WriteLine(\"Success\");" + }; + + filePreprocessor.ParseScript(lines, new FileParserContext()); + loadLineProcessor.InheritedProcessLineCalled.ShouldBeTrue(); + } + + private IFilePreProcessor GetFilePreProcessor(ILineProcessor customDirectiveProcessor, ILineProcessor loadLineProcessor) + { + var lineProcessors = new ILineProcessor[] + { + new UsingLineProcessor(), + new ReferenceLineProcessor(_fileSystem.Object), + loadLineProcessor, + new ShebangLineProcessor(), + customDirectiveProcessor + }; + + return new FilePreProcessor(_fileSystem.Object, new TestLogProvider(), lineProcessors); + } + + public class TestableLoadLineProcessor : LoadLineProcessor + { + public TestableLoadLineProcessor(IFileSystem fileSystem) + : base(fileSystem) + { } + public bool InheritedProcessLineCalled { get; private set; } + protected override bool ProcessLine(IFileParser parser, FileParserContext context, string line) + { + InheritedProcessLineCalled = true; + return true; + } } } } diff --git a/test/ScriptCs.Core.Tests/FileProcessorTests.cs.orig b/test/ScriptCs.Core.Tests/FileProcessorTests.cs.orig deleted file mode 100644 index 93913a57..00000000 --- a/test/ScriptCs.Core.Tests/FileProcessorTests.cs.orig +++ /dev/null @@ -1,476 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Moq; -using Should; -using Xunit; - -namespace ScriptCs.Tests -{ - public class FileProcessorTests - { -<<<<<<< HEAD - public class TheProcessFileMethod - { - private List file1 = new List - { - "using System;", - @"Console.WriteLine(""Hello Script 1"");", - @"Console.WriteLine(""Loading Script 2"");", - @"#load ""script2.csx""", - @"Console.WriteLine(""Loaded Script 2"");", - @"Console.WriteLine(""Loading Script 4"");", - @"#load ""script4.csx"";", - @"Console.WriteLine(""Loaded Script 4"");", - @"Console.WriteLine(""Goodbye Script 1"");" - }; - - private List file2 = new List - { - "using System;", - @"Console.WriteLine(""Hello Script 2"");", - @"Console.WriteLine(""Loading Script 3"");", - @"#load ""script3.csx""", - @"Console.WriteLine(""Loaded Script 3"");", - @"Console.WriteLine(""Goodbye Script 2"");" - }; - - private List file3 = new List -======= - public class ProcessFileMethod - { - private List _file1 = new List - { - @"#load ""script2.csx""", - @"#load ""script4.csx"";", - "using System;", - @"Console.WriteLine(""Hello Script 1"");", - @"Console.WriteLine(""Loading Script 2"");", - @"Console.WriteLine(""Loaded Script 2"");", - @"Console.WriteLine(""Loading Script 4"");", - @"Console.WriteLine(""Loaded Script 4"");", - @"Console.WriteLine(""Goodbye Script 1"");" - }; - - private List _file2 = new List - { - "using System;", - @"Console.WriteLine(""Hello Script 2"");", - @"Console.WriteLine(""Loading Script 3"");", - @"#load ""script3.csx""", - @"Console.WriteLine(""Loaded Script 3"");", - @"Console.WriteLine(""Goodbye Script 2"");" - }; - - private readonly List _file3 = new List - { - "using System;", - "using System.Collections.Generic;", - @"Console.WriteLine(""Hello Script 3"");", - @"Console.WriteLine(""Goodbye Script 3"");" - }; - - private readonly List _file4 = new List - { - "using System;", - "using System.Core;", - @"Console.WriteLine(""Hello Script 4"");", - @"Console.WriteLine(""Goodbye Script 4"");" - }; - - private readonly Mock _fileSystem; - - public ProcessFileMethod() - { - _fileSystem = new Mock(); - _fileSystem.SetupGet(x => x.NewLine).Returns(Environment.NewLine); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script1.csx"))) - .Returns(_file1.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script2.csx"))) - .Returns(_file2.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script3.csx"))) - .Returns(_file3.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script4.csx"))) - .Returns(_file4.ToArray()); - } - - [Fact] - public void MultipleUsingStatementsShouldProduceDistinctOutput() - { - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None); - - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i.StartsWith("\\script"))), Times.Exactly(3)); - Assert.Equal(2, splitOutput.Count(x => x.TrimStart(' ').StartsWith("using "))); - } - - [Fact] - public void UsingStateMentsShoulAllBeAtTheTop() ->>>>>>> e46d53e6a34d0ae5aadc5876ae63d9d8af3e278e - { - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - -<<<<<<< HEAD - private List file4 = new List -======= - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None); - var lastUsing = splitOutput.ToList().FindLastIndex(x => x.TrimStart(' ').StartsWith("using ")); - var firsNotUsing = splitOutput.ToList().FindIndex(x => !x.TrimStart(' ').StartsWith("using ")); - - Assert.True(lastUsing < firsNotUsing); - } - - [Fact] - public void ShouldNotLoadInlineLoads() ->>>>>>> e46d53e6a34d0ae5aadc5876ae63d9d8af3e278e - { - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - -<<<<<<< HEAD - private readonly Mock _fileSystem; - - public TheProcessFileMethod() - { - _fileSystem = new Mock(); - _fileSystem.SetupGet(x => x.NewLine).Returns(Environment.NewLine); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script1.csx"))) - .Returns(file1.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script2.csx"))) - .Returns(file2.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script3.csx"))) - .Returns(file3.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script4.csx"))) - .Returns(file4.ToArray()); - } - - [Fact] - public void MultipleUsingStatementsShouldProduceDistinctOutput() - { - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - - var splitOutput = output.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i.StartsWith("\\script"))), Times.Exactly(4)); - Assert.Equal(3, splitOutput.Count(x => x.TrimStart(' ').StartsWith("using "))); - } - - [Fact] - public void UsingStateMentsShoulAllBeAtTheTop() - { - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - - var splitOutput = output.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - var lastUsing = splitOutput.ToList().FindLastIndex(x => x.TrimStart(' ').StartsWith("using ")); - var firsNotUsing = splitOutput.ToList().FindIndex(x => !x.TrimStart(' ').StartsWith("using ")); - - Assert.True(lastUsing < firsNotUsing); - } - - [Fact] - public void ThreeLoadedFilesShoulAllBeCalledOnce() - { - var processor = new FilePreProcessor(_fileSystem.Object); - processor.ProcessFile("\\script1.csx"); - - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script1.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script2.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script3.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script4.csx")), Times.Once()); - } - - [Fact] - public void LoadBeforeUsingShouldBeAllowed() - { - var file = new List - { - @"#load ""script4.csx""", - "", - "using System;", - @"Console.WriteLine(""abc"");" - }; -======= - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script1.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script2.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script3.csx")), Times.Never()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script4.csx")), Times.Once()); - } - - [Fact] - public void ShouldNotLoadSameFileTwice() - { - var file = new List - { - @"#load ""script4.csx""", - "using System;", - @"Console.WriteLine(""Hello Script 2"");", - }; - - var fs = new Mock(); - fs.Setup(i => i.NewLine).Returns(Environment.NewLine); - fs.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script2.csx"))) - .Returns(file.ToArray()); - fs.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script4.csx"))) - .Returns(_file4.ToArray()); - - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script1.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script2.csx")), Times.Once()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script3.csx")), Times.Never()); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script4.csx")), Times.Once()); - } - - [Fact] - public void LoadBeforeUsingShouldBeAllowed() - { - var file = new List - { - @"#load ""script4.csx""", - "", - "using System;", - @"Console.WriteLine(""abc"");" - }; - - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\file.csx"))).Returns(file.ToArray()); - - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\file.csx"); - - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None); - var lastUsing = splitOutput.ToList().FindLastIndex(x => x.TrimStart(' ').StartsWith("using ")); - var firsNotUsing = splitOutput.ToList().FindIndex(x => !x.TrimStart(' ').StartsWith("using ")); - - splitOutput.Count(x => x.TrimStart(' ').StartsWith("using ")).ShouldEqual(2); - Assert.True(lastUsing < firsNotUsing); - } - - [Fact] - public void ShouldNotBeAllowedToLoadAfterUsing() - { - var file = new List - { - "using System;", - @"Console.WriteLine(""abc"");", - @"#load ""script4.csx""" - }; ->>>>>>> e46d53e6a34d0ae5aadc5876ae63d9d8af3e278e - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\file.csx"))).Returns(file.ToArray()); - - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\file.csx"); - -<<<<<<< HEAD - var splitOutput = output.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - var lastUsing = splitOutput.ToList().FindLastIndex(x => x.TrimStart(' ').StartsWith("using ")); - var firsNotUsing = splitOutput.ToList().FindIndex(x => !x.TrimStart(' ').StartsWith("using ")); - - Assert.Equal(2, splitOutput.Count(x => x.TrimStart(' ').StartsWith("using "))); - Assert.True(lastUsing < firsNotUsing); -======= - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None); - - Assert.Equal(1, splitOutput.Count(x => x.TrimStart(' ').StartsWith("using "))); - Assert.Equal(3, splitOutput.Length); - _fileSystem.Verify(x => x.ReadFileLines(It.Is(i => i == "\\script3.csx")), Times.Never()); ->>>>>>> e46d53e6a34d0ae5aadc5876ae63d9d8af3e278e - } - - [Fact] - public void UsingInCodeDoesNotCountAsUsingImport() - { - var file = new List -<<<<<<< HEAD - { - @"#load ""script4.csx""", - "", - "using System;", - "using System.IO;", - "Console.WriteLine();", - @"using (var stream = new MemoryStream) {", - @"//do stuff", - @"}" - }; - - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\file.csx"))).Returns(file.ToArray()); - - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\file.csx"); - - var splitOutput = output.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - var firstNonImportUsing = splitOutput.ToList().FindIndex(x => x.TrimStart(' ').StartsWith("using ") && !x.Contains(";")); - var firstCodeLine = splitOutput.ToList().FindIndex(x => x.Contains("Console")); - - Assert.True(firstNonImportUsing > firstCodeLine); - } - - [Fact] - public void ShouldAddLineDirectiveRightAfterLastLoadIsIncludedInEachFile() - { - // f1 has usings and then loads - var f1 = new List - { - "using System;", - "using System.Diagnostics;", - @"#load ""C:\f2.csx"";", - @"#load ""C:\f3.csx"";", - @"Console.WriteLine(""First line of f1"");", - }; - - // f2 has no usings and multiple loads - var f2 = new List - { - @"#load ""C:\f4.csx"";", - @"#load ""C:\f5.csx"";", - @"Console.WriteLine(""First line of f2"");", - }; - - // f3 has usings and no loads - var f3 = new List - { - @"using System;", - @"using System.Diagnostics;", - @"Console.WriteLine(""First line of f3"");", - }; - - // f4 has no usings and no loads - var f4 = new List - { - @"Console.WriteLine(""First line of f4"");", - }; - - // f5 is no special case, just used to be loaded - var f5 = new List - { - @"using System;", - @"Console.WriteLine(""First line of f5"");", - }; - - _fileSystem.SetupGet(fs => fs.NewLine).Returns(Environment.NewLine); - _fileSystem.Setup(fs => fs.ReadFileLines(@"C:\f1.csx")) - .Returns(f1.ToArray()); - _fileSystem.Setup(fs => fs.ReadFileLines(@"C:\f2.csx")) - .Returns(f2.ToArray()).Verifiable(); - _fileSystem.Setup(fs => fs.ReadFileLines(@"C:\f3.csx")) - .Returns(f3.ToArray()); - _fileSystem.Setup(fs => fs.ReadFileLines(@"C:\f4.csx")) - .Returns(f4.ToArray()); - _fileSystem.Setup(fs => fs.ReadFileLines(@"C:\f5.csx")) - .Returns(f5.ToArray()); - _fileSystem.Setup(fs => fs.IsPathRooted(It.IsAny())).Returns(true); - - var preProcessor = new FilePreProcessor(_fileSystem.Object); - - var file = preProcessor.ProcessFile(@"C:\f1.csx"); - - var fileLines = file.Split(new[]{ Environment.NewLine }, StringSplitOptions.None); - - // using statements go first, after that f4 -> f5 -> f2 -> f3 -> f1 - var line = 0; - fileLines[line++].ShouldEqual("using System;"); - fileLines[line++].ShouldEqual("using System.Diagnostics;"); - - fileLines[line++].ShouldEqual(@"#line 1 ""C:\f4.csx"""); - fileLines[line++].ShouldEqual(f4[0]); - - fileLines[line++].ShouldEqual(@"#line 2 ""C:\f5.csx"""); - fileLines[line++].ShouldEqual(f5[1]); - - fileLines[line++].ShouldEqual(@"#line 3 ""C:\f2.csx"""); - fileLines[line++].ShouldEqual(f2[2]); - - fileLines[line++].ShouldEqual(@"#line 3 ""C:\f3.csx"""); - fileLines[line++].ShouldEqual(f3[2]); - - fileLines[line++].ShouldEqual(@"#line 5 ""C:\f1.csx"""); - fileLines[line].ShouldEqual(f1[4]); -======= - { - @"#load ""script4.csx""", - "", - "using System;", - "using System.IO;", - "Console.WriteLine();", - @"using (var stream = new MemoryStream) {", - @"//do stuff", - @"}" - }; - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\file.csx"))).Returns(file.ToArray()); - - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\file.csx"); - - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None); - var firstNonImportUsing = - splitOutput.ToList().FindIndex(x => x.TrimStart(' ').StartsWith("using ") && !x.Contains(";")); - var firstCodeLine = splitOutput.ToList().FindIndex(x => x.Contains("Console")); - - Assert.True(firstNonImportUsing > firstCodeLine); - } - - [Fact] - public void ShouldHaveReferencesOnTop() - { - var file1 = new List - { - @"#r ""My.dll""", - @"#load ""script2.csx""", - "using System;", - @"Console.WriteLine(""Hi!"");" - }; - - var fs = new Mock(); - fs.Setup(i => i.NewLine).Returns(Environment.NewLine); - fs.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script1.csx"))).Returns(file1.ToArray()); - fs.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script2.csx"))).Returns(_file2.ToArray()); - - var processor = new FilePreProcessor(fs.Object); - var output = processor.ProcessFile("\\script1.csx"); - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None).ToList(); - - var lastR = splitOutput.FindLastIndex(line => line.StartsWith("#r ")); - var firstNotR = splitOutput.FindIndex(line => !line.StartsWith("#r ")); - - lastR.ShouldNotEqual(-1); - Assert.True(lastR < firstNotR); - } - - [Fact] - public void ShouldHaveReferencesFromAllFiles() - { - var file1 = new List - { - @"#r ""My.dll""", - @"#load ""scriptX.csx""", - "using System;", - @"Console.WriteLine(""Hi!"");" - }; - - var file2 = new List - { - @"#r ""My2.dll""", - "using System;", - @"Console.WriteLine(""Hi!"");" - }; - - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\script1.csx"))) - .Returns(file1.ToArray()); - _fileSystem.Setup(x => x.ReadFileLines(It.Is(f => f == "\\scriptX.csx"))) - .Returns(file2.ToArray()); - - var processor = new FilePreProcessor(_fileSystem.Object); - var output = processor.ProcessFile("\\script1.csx"); - - var splitOutput = output.Split(new[] {Environment.NewLine}, StringSplitOptions.None); - splitOutput.Count(line => line.StartsWith("#r ")).ShouldEqual(2); ->>>>>>> e46d53e6a34d0ae5aadc5876ae63d9d8af3e278e - } - } - } -} diff --git a/test/ScriptCs.Core.Tests/FileSystemTests.cs b/test/ScriptCs.Core.Tests/FileSystemTests.cs index 9373bc7d..f53667f3 100644 --- a/test/ScriptCs.Core.Tests/FileSystemTests.cs +++ b/test/ScriptCs.Core.Tests/FileSystemTests.cs @@ -50,9 +50,13 @@ public void ShouldReturnWorkingDirectoryIfPathIsInvalid() [Fact] public void ReturnsCorrectWorkingDirectory() { - string workingDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @".\working_dir\")); - string existingDirectoryPath = Path.GetFullPath(Path.Combine(workingDir, @".\existing_dir\")); - string existingFilePath = Path.GetFullPath(Path.Combine(workingDir, @".\existing_file.txt")); + var workingPath = string.Format(@".{0}working_dir", Path.DirectorySeparatorChar); + var existingPath = string.Format(@".{0}existing_dir{0}", Path.DirectorySeparatorChar); + var existingFile = string.Format(@".{0}existing_file.txt", Path.DirectorySeparatorChar); + + string workingDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, workingPath)); + string existingDirectoryPath = Path.GetFullPath(Path.Combine(workingDir, existingPath)); + string existingFilePath = Path.GetFullPath(Path.Combine(workingDir, existingFile)); try { @@ -62,7 +66,7 @@ public void ReturnsCorrectWorkingDirectory() _fileSystem.GetWorkingDirectory(existingDirectoryPath).ShouldEqual(existingDirectoryPath); _fileSystem.GetWorkingDirectory(existingFilePath).ShouldEqual( - Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @".\working_dir"))); + Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, workingDir))); } finally { @@ -86,9 +90,12 @@ public void ReturnsCorrectWorkingDirectory() [Fact] public void ReturnsCorrectWorkingDirectoryIfPathDoesNotExist() { - const string NonExistantFilePath = @"C:\working_dir\i_dont_exist.txt"; + var tempPath = Path.GetTempPath(); + + var nonExistantFilePath = Path.Combine(tempPath, "i_dont_exist.txt"); - _fileSystem.GetWorkingDirectory(NonExistantFilePath).ShouldEqual(@"C:\working_dir"); + _fileSystem.GetWorkingDirectory(nonExistantFilePath) + .ShouldEqual(tempPath.TrimEnd(Path.DirectorySeparatorChar)); } } diff --git a/test/ScriptCs.Core.Tests/LoadLineProcessorTests.cs b/test/ScriptCs.Core.Tests/LoadLineProcessorTests.cs index 9950ea58..6a37a696 100644 --- a/test/ScriptCs.Core.Tests/LoadLineProcessorTests.cs +++ b/test/ScriptCs.Core.Tests/LoadLineProcessorTests.cs @@ -1,16 +1,11 @@ using System; - using Moq; - -using Ploeh.AutoFixture.Xunit; - using ScriptCs.Contracts; - using Should; - -using Xunit.Extensions; using Should.Core.Assertions; using ScriptCs.Contracts.Exceptions; +using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -69,7 +64,7 @@ public void ShouldThrowExceptionIfAfterCode( fileSystem.Setup(x => x.GetFullPath(RelativePath)).Returns(FullPath); // Act / Assert - Assert.Throws(typeof(InvalidDirectiveUseException), () => processor.ProcessLine(parser.Object, context, Line, false)); + Xunit.Assert.Throws(() => processor.ProcessLine(parser.Object, context, Line, false)); } [Theory, ScriptCsAutoData] diff --git a/test/ScriptCs.Core.Tests/PackageAssemblyResolverTests.cs b/test/ScriptCs.Core.Tests/PackageAssemblyResolverTests.cs index f42b6784..89ad9385 100644 --- a/test/ScriptCs.Core.Tests/PackageAssemblyResolverTests.cs +++ b/test/ScriptCs.Core.Tests/PackageAssemblyResolverTests.cs @@ -1,16 +1,14 @@ -using System; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.Versioning; -using Common.Logging; using Moq; using NuGet; using ScriptCs.Contracts; -using ScriptCs.Exceptions; -using Should; +using Should; using Xunit; using IFileSystem = ScriptCs.Contracts.IFileSystem; -using PackageReference = ScriptCs.Package.PackageReference; namespace ScriptCs.Tests { @@ -18,24 +16,27 @@ public class PackageAssemblyResolverTests { public class GetAssemblyNamesMethod { - private readonly Mock _logger; + private readonly TestLogProvider _logProvider=new TestLogProvider(); private readonly Mock _filesystem; private readonly Mock _package; private readonly Mock _packageContainer; + private readonly Mock _assemblyUtility; private readonly List _packageIds; private readonly string _workingDirectory; public GetAssemblyNamesMethod() { - _workingDirectory = "c:\\test"; + _workingDirectory = Path.GetTempPath(); _filesystem = new Mock(); - _filesystem.SetupGet(i => i.CurrentDirectory).Returns("c:\\test"); + + _filesystem.SetupGet(i => i.CurrentDirectory).Returns(_workingDirectory); + _filesystem.SetupGet(i => i.PackagesFile).Returns("packages.config"); + _filesystem.SetupGet(i => i.PackagesFolder).Returns("packages"); + _filesystem.Setup(i => i.DirectoryExists(It.IsAny())).Returns(true); _filesystem.Setup(i => i.FileExists(It.IsAny())).Returns(true); - _logger = new Mock(); - _package = new Mock(); _package.Setup(i => i.GetCompatibleDlls(It.IsAny())) .Returns(new List { "test.dll", "test2.dll" }); @@ -43,6 +44,7 @@ public GetAssemblyNamesMethod() _package.SetupGet(i => i.Version).Returns(new Version("3.0")); _package.SetupGet(i => i.TextVersion).Returns("3.0"); _package.SetupGet(i => i.FullName).Returns(_package.Object.Id + "." + _package.Object.Version); + _package.SetupGet(i => i.FrameworkAssemblies).Returns(new[] { "System.Net.Http", "System.ComponentModel" }); _packageIds = new List { @@ -52,18 +54,21 @@ public GetAssemblyNamesMethod() _packageContainer = new Mock(); _packageContainer.Setup(i => i.FindReferences(It.IsAny())).Returns(_packageIds); _packageContainer.Setup(i => i.FindPackage(It.IsAny(), It.IsAny())).Returns(_package.Object); + + _assemblyUtility = new Mock(); + _assemblyUtility.Setup(u => u.IsManagedAssembly(It.IsAny())).Returns(true); } [Fact] public void WhenManyPackagesAreMatchedAllMatchingDllsWithUniquePathsShouldBeReturned() { - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); _packageIds.Add(new PackageReference("testId2", VersionUtility.ParseFrameworkName("net40"), new Version("3.0"))); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(2); + found.Count.ShouldEqual(4); } [Fact] @@ -80,11 +85,11 @@ public void WhenManyPackagesAreMatchedAllMatchingDllsShouldBeReturned() _packageContainer.Setup(i => i.FindPackage(It.IsAny(), It.Is(x => x.PackageId == "testId2"))).Returns(p.Object); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(4); + found.Count.ShouldEqual(6); } [Fact] @@ -96,12 +101,12 @@ public void WhenPackageIsMatchedItsNonMatchingDllsShouldBeExcluded() It.Is(x => x.FullName == VersionUtility.ParseFrameworkName("net40").FullName))) .Returns(new List { "test.dll" }); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(1); + found.Count.ShouldEqual(3); } [Fact] @@ -124,50 +129,63 @@ public void WhenManyPackagesAreMatchedAllNonMatchingDllsShouldBeExcluded() _packageContainer.Setup(i => i.FindPackage(It.IsAny(), It.Is(x => x.PackageId == "testId2"))).Returns(p.Object); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(2); + found.Count.ShouldEqual(4); } [Fact] public void WhenDllsAreMatchedDllFilePathsAreCorrectlyConcatenated() { - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory); - found.First().ShouldEqual("c:\\test\\packages\\id.3.0\\test.dll"); - found.ElementAt(1).ShouldEqual("c:\\test\\packages\\id.3.0\\test2.dll"); + found.First().ShouldEqual( + Path.Combine(_workingDirectory, "packages", "id.3.0", "test.dll")); + found.ElementAt(1).ShouldEqual( + Path.Combine(_workingDirectory, "packages", "id.3.0", "test2.dll")); } [Fact] - public void WhenNoPackagesAreFoundShouldThrowArgumentEx() + public void WhenNoPackagesAreFoundShouldLogWarning() { + // arrange _packageContainer.Setup(i => i.FindPackage(It.IsAny(), It.IsAny())) .Returns>(null); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); + + // act + resolver.GetAssemblyNames(_workingDirectory); - Assert.Throws(() => resolver.GetAssemblyNames(_workingDirectory)); + // assert + _logProvider.Output.ShouldContain("WARN: Cannot find: testId 3.0"); } [Fact] - public void WhenPackagesAreFoundButNoMatchingDllsExistShouldThrowArgumentEx() + public void WhenPackagesAreFoundButNoMatchingDllsExistShouldLogWarning() { + // arrange _package.Setup(i => i.GetCompatibleDlls(It.IsAny())).Returns>(null); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); + + // act + resolver.GetAssemblyNames(_workingDirectory); - Assert.Throws(() => resolver.GetAssemblyNames(_workingDirectory)); + // assert + _logProvider.Output.ShouldContain( + "WARN: Cannot find compatible binaries for .NETFramework,Version=v4.0 in: testId 3.0"); } [Fact] public void WhenPackageDirectoryDoesNotExistShouldReturnEmptyPackagesList() { - var resolver = new PackageAssemblyResolver(_filesystem.Object, new Mock().Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, new Mock().Object, _logProvider, _assemblyUtility.Object); _filesystem.Setup(i => i.DirectoryExists(It.IsAny())).Returns(false); var found = resolver.GetAssemblyNames(_workingDirectory); @@ -177,7 +195,7 @@ public void WhenPackageDirectoryDoesNotExistShouldReturnEmptyPackagesList() [Fact] public void WhenPackagesConfigDoesNotExistShouldReturnEmptyPackagesList() { - var resolver = new PackageAssemblyResolver(_filesystem.Object, new Mock().Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, new Mock().Object, _logProvider, _assemblyUtility.Object); _filesystem.Setup(i => i.FileExists(It.IsAny())).Returns(false); var found = resolver.GetAssemblyNames(_workingDirectory); @@ -200,13 +218,13 @@ public void ShouldLoadAllDependenciesIfPackageHasAny() i => i.FindPackage(It.IsAny(), It.Is(x => x.PackageId == "p2"))) .Returns(p.Object); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); _packageContainer.Verify(i => i.FindPackage(It.IsAny(), It.IsAny()), Times.Exactly(2)); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(4); + found.Count.ShouldEqual(6); } [Fact] @@ -227,13 +245,13 @@ public void ShouldNotLoadDuplicateDependencies() i => i.FindPackage(It.IsAny(), It.Is(x => x.PackageId == "p2"))) .Returns(p.Object); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); _packageContainer.Verify(i => i.FindPackage(It.IsAny(), It.IsAny()), Times.Exactly(2)); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(4); + found.Count.ShouldEqual(6); } [Fact] @@ -252,13 +270,26 @@ public void ShouldNotLoadIncompatibleDependenciesDlls() i => i.FindPackage(It.IsAny(), It.Is(x => x.PackageId == "p2"))) .Returns(p.Object); - var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); _packageContainer.Verify(i => i.FindPackage(It.IsAny(), It.IsAny()), Times.Exactly(2)); found.ShouldNotBeEmpty(); - found.Count.ShouldEqual(2); + found.Count.ShouldEqual(4); + } + + [Fact] + public void ShouldIgnoreUnmanagedAssemblies() + { + var resolver = new PackageAssemblyResolver(_filesystem.Object, _packageContainer.Object, _logProvider, _assemblyUtility.Object); + _packageIds.Add(new PackageReference("testId2", VersionUtility.ParseFrameworkName("net40"), new Version("3.0"))); + _assemblyUtility.Setup(u => u.IsManagedAssembly(It.Is(s => Path.GetFileName(s) == "test.dll"))).Returns(false); + + var found = resolver.GetAssemblyNames(_workingDirectory).ToList(); + + found.ShouldNotBeEmpty(); + found.Count.ShouldEqual(3); } } @@ -266,13 +297,17 @@ public class GetPackagesMethod { private Mock _fs; private Mock _pc; - private Mock _logger; + private TestLogProvider _logProvider = new TestLogProvider(); + private Mock _assemblyUtility; public GetPackagesMethod() { _fs = new Mock(); + _fs.SetupGet(f => f.PackagesFolder).Returns("packages"); + _fs.SetupGet(f => f.PackagesFile).Returns("packages.config"); + _pc = new Mock(); - _logger = new Mock(); + _assemblyUtility = new Mock(); } [Fact] @@ -280,7 +315,7 @@ public void ShouldReturnEmptyIfPackagesConfigDoesNotExist() { _fs.Setup(i => i.FileExists(It.IsAny())).Returns(false); - var resolver = new PackageAssemblyResolver(_fs.Object, _pc.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_fs.Object, _pc.Object, _logProvider, _assemblyUtility.Object); var result = resolver.GetPackages(@"c:/"); result.ShouldBeEmpty(); @@ -292,7 +327,7 @@ public void ShouldGetReferencesToPackages() _fs.Setup(i => i.FileExists(It.IsAny())).Returns(true); _pc.Setup(i => i.FindReferences(It.IsAny())).Returns(new List { new PackageReference("id", VersionUtility.ParseFrameworkName("net40"), new Version("3.0")) }); - var resolver = new PackageAssemblyResolver(_fs.Object, _pc.Object, _logger.Object); + var resolver = new PackageAssemblyResolver(_fs.Object, _pc.Object, _logProvider, _assemblyUtility.Object); var result = resolver.GetPackages(@"c:/"); _pc.Verify(i => i.FindReferences(It.IsAny()), Times.Once()); @@ -304,32 +339,22 @@ public class SavePackagesMethod { private Mock _fs; private Mock _pc; - private Mock _logger; + private readonly TestLogProvider _logProvider = new TestLogProvider(); + private Mock _assemblyUtility; public SavePackagesMethod() { _fs = new Mock(); + _fs.SetupGet(f => f.PackagesFile).Returns("packages.config"); + _fs.SetupGet(f => f.PackagesFolder).Returns("packages"); + _pc = new Mock(); - _logger = new Mock(); + _assemblyUtility = new Mock(); } private IPackageAssemblyResolver GetResolver() { - return new PackageAssemblyResolver(_fs.Object, _pc.Object, _logger.Object); - } - - [Fact] - public void ShouldNotSaveWhenThereIsPackagesFile() - { - _fs.Setup(i => i.CurrentDirectory).Returns("C:/"); - _fs.Setup(i => i.FileExists(It.IsAny())).Returns(true); - _fs.Setup(i => i.DirectoryExists(It.IsAny())).Returns(true); - var resolver = GetResolver(); - - resolver.SavePackages(); - - _pc.Verify(i => i.CreatePackageFile(), Times.Never()); - _logger.Verify(i => i.Info(It.Is(x => x == "Packages.config already exists!")), Times.Once()); + return new PackageAssemblyResolver(_fs.Object, _pc.Object, _logProvider, _assemblyUtility.Object); } [Fact] @@ -343,38 +368,19 @@ public void ShouldNotSaveWhenThereIsNoPackagesFolder() resolver.SavePackages(); _pc.Verify(i => i.CreatePackageFile(), Times.Never()); - _logger.Verify(i => i.Info(It.Is(x => x == "Packages directory does not exist!")), Times.Once()); + _logProvider.Output.ShouldContain("WARN: Packages directory does not exist!"); } [Fact] public void ShouldSaveWhenThereIsNoPackagesConfigAndThereIsPackagesFolder() { _fs.Setup(i => i.CurrentDirectory).Returns("C:/"); - _fs.Setup(i => i.FileExists(It.IsAny())).Returns(false); - _fs.Setup(i => i.DirectoryExists(It.IsAny())).Returns(true); - _pc.Setup(i => i.CreatePackageFile()).Returns(new List { "package" }); - var resolver = GetResolver(); - - resolver.SavePackages(); - - _pc.Verify(i => i.CreatePackageFile(), Times.Once()); - _logger.Verify(i => i.Info(It.Is(x => x == "Packages.config successfully created!")), Times.Once()); - _logger.Verify(i => i.Info(It.Is(x => x == "Added package")), Times.Once()); - } - - [Fact] - public void ShouldDisplayErrorWhenNoPackagesFound() - { - _fs.Setup(i => i.CurrentDirectory).Returns("C:/"); - _fs.Setup(i => i.FileExists(It.IsAny())).Returns(false); _fs.Setup(i => i.DirectoryExists(It.IsAny())).Returns(true); - _pc.Setup(i => i.CreatePackageFile()).Returns(new List()); var resolver = GetResolver(); resolver.SavePackages(); _pc.Verify(i => i.CreatePackageFile(), Times.Once()); - _logger.Verify(i => i.Info(It.Is(x => x == "No packages found!")), Times.Once()); } } } diff --git a/test/ScriptCs.Core.Tests/PackageReferenceTests.cs b/test/ScriptCs.Core.Tests/PackageReferenceTests.cs new file mode 100644 index 00000000..89c044b9 --- /dev/null +++ b/test/ScriptCs.Core.Tests/PackageReferenceTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.Versioning; +using Should; +using Xunit; + +namespace ScriptCs.Tests +{ + public class PackageReferenceTests + { + public class TheConstructor + { + [Theory] + [InlineData(null, null, null)] + [InlineData("", null, null)] + [InlineData(" ", null, null)] + [InlineData("1.0.1", "1.0.1", null)] + [InlineData("1.0.1-alpha", "1.0.1", "alpha")] + [InlineData("1.0.1-beta-2", "1.0.1", "beta-2")] + public void ParsesVersionAndSpecialVersion( + string version, string expectedVersion, string expectedSpecialVersion) + { + // Arrange, Act + var packageReference = new PackageReference( + "packageId", new FrameworkName(".NETFramework,Version=v4.0"), version); + + // Assert + packageReference.Version.ShouldEqual(string.IsNullOrWhiteSpace(expectedVersion) + ? new Version() + : new Version(expectedVersion)); + + packageReference.SpecialVersion.ShouldEqual(expectedSpecialVersion); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/Properties/AssemblyInfo.cs b/test/ScriptCs.Core.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index febbab00..00000000 --- a/test/ScriptCs.Core.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("ScriptCs.Core.Tests")] -[assembly: AssemblyDescription("")] - -[assembly: Guid("fe718b7f-5fb4-43eb-a3da-26a1d25e644c")] diff --git a/test/ScriptCs.Core.Tests/ReferenceLineProcessorTests.cs b/test/ScriptCs.Core.Tests/ReferenceLineProcessorTests.cs index d629bc00..cdf606a4 100644 --- a/test/ScriptCs.Core.Tests/ReferenceLineProcessorTests.cs +++ b/test/ScriptCs.Core.Tests/ReferenceLineProcessorTests.cs @@ -1,12 +1,12 @@ using System; using Moq; -using Ploeh.AutoFixture.Xunit; using ScriptCs.Contracts; using Should; -using Xunit.Extensions; using System.Linq; using Should.Core.Assertions; using ScriptCs.Contracts.Exceptions; +using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -63,9 +63,9 @@ public void ShouldThrowExceptionIfAfterCode( const string FullPath = "C:\\script.csx"; fileSystem.Setup(x => x.GetFullPath(RelativePath)).Returns(FullPath); - + // Act / Assert - Assert.Throws(typeof(InvalidDirectiveUseException), () => processor.ProcessLine(parser, context, Line, false)); + Xunit.Assert.Throws(() => processor.ProcessLine(parser, context, Line, false)); } [Theory, ScriptCsAutoData] @@ -107,7 +107,7 @@ public void ShouldAddReferenceByNameFromGACIfLocalFileDoesntExist( fileSystem.Setup(x => x.FileExists(fullPath)).Returns(false); // Act - var result = processor.ProcessLine(parser, context, line, true); + processor.ProcessLine(parser, context, line, true); // Assert context.References.Count(x => x == name).ShouldEqual(1); diff --git a/test/ScriptCs.Core.Tests/ReplCommands/AliasCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/AliasCommandTests.cs new file mode 100644 index 00000000..7b6203c8 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/AliasCommandTests.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.IO; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Should; +using Xunit; +using Xunit.Extensions; + +namespace ScriptCs.Tests.ReplCommands +{ + public class AliasCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ShouldReturnAlias() + { + // act + var cmd = new AliasCommand(new Mock().Object); + + // assert + cmd.CommandName.ShouldEqual("alias"); + } + } + + public class ExecuteMethod + { + [Theory, ScriptCsAutoData] + public void ShouldAliasCommandWithNewName( + Mock fileSystem, + Mock engine, + Mock serializer, + TestLogProvider logProvider, + Mock composer, + Mock console, + Mock filePreProcessor) + { + // arrange + var currentDir = @"C:\"; + var dummyCommand = new Mock(); + dummyCommand.Setup(x => x.CommandName).Returns("foo"); + + fileSystem.Setup(x => x.BinFolder).Returns(Path.Combine(currentDir, "bin")); + fileSystem.Setup(x => x.DllCacheFolder).Returns(Path.Combine(currentDir, "cache")); + + var executor = new Repl( + new string[0], + fileSystem.Object, + engine.Object, + serializer.Object, + logProvider, + composer.Object, + console.Object, + filePreProcessor.Object, + new List { dummyCommand.Object }, + new Printers(serializer.Object), + new ScriptInfo()); + + var cmd = new AliasCommand(console.Object); + + // act + cmd.Execute(executor, new[] { "foo", "bar" }); + + // assert + executor.Commands.Count.ShouldEqual(2); + executor.Commands["bar"].ShouldBeSameAs(executor.Commands["foo"]); + } + + [Fact] + public void ShouldNotThrowAnExceptionWhenAnUnknownCommandIsPassed() + { + // arrange + var command = new AliasCommand(new Mock().Object); + + // act + var exception = Record.Exception(() => command.Execute(new Mock().Object, null)); + + // assert + exception.ShouldBeNull(); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/ReplCommands/CdCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/CdCommandTests.cs new file mode 100644 index 00000000..a7c7e1e8 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/CdCommandTests.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class CdCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsCd() + { + // act + var cmd = new CdCommand(); + + // assert + Assert.Equal("cd", cmd.CommandName); + } + } + + public class ExecuteMethod + { + [Fact] + public void ChangesRelativePathBasedOnArg() + { + // arrange + var fs = new Mock(); + var repl = new Mock(); + + var tempPath = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar); + + fs.Setup(x => x.CurrentDirectory).Returns(Path.Combine(tempPath, "dir")); + repl.Setup(x => x.FileSystem).Returns(fs.Object); + + var cmd = new CdCommand(); + + // act + cmd.Execute(repl.Object, new[] { ".." }); + + // assert + fs.VerifySet(x => x.CurrentDirectory = tempPath, Times.Once()); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/ClearCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/ClearCommandTests.cs new file mode 100644 index 00000000..e6b1f383 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/ClearCommandTests.cs @@ -0,0 +1,40 @@ +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class ClearCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsClear() + { + // act + var cmd = new ClearCommand(new Mock().Object); + + // assert + Assert.Equal("clear", cmd.CommandName); + } + } + + public class ExecuteMethod + { + [Fact] + public void CallsConsoleClear() + { + // arrange + var console = new Mock(); + var cmd = new ClearCommand(console.Object); + + // act + cmd.Execute(new Mock().Object, null); + + // assert + console.Verify(x => x.Clear(), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/CwdCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/CwdCommandTests.cs new file mode 100644 index 00000000..3124ced0 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/CwdCommandTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class CwdCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsCwd() + { + // act + var cmd = new CwdCommand(new Mock().Object); + + // assert + Assert.Equal("cwd", cmd.CommandName); + } + } + + public class ExecuteMethod + { + [Fact] + public void PrintsCurrentWorkingDirectoryToConsole() + { + // arrange + var console = new Mock(); + var fs = new Mock(); + var repl = new Mock(); + + var tempPath = Path.GetTempPath(); + + fs.Setup(x => x.CurrentDirectory).Returns(tempPath); + repl.Setup(x => x.FileSystem).Returns(fs.Object); + + var cmd = new CwdCommand(console.Object); + + // act + cmd.Execute(repl.Object, null); + + // assert + console.Verify(x => x.WriteLine(tempPath)); + } + + [Fact] + public void PreservesConsoleColors() + { + // arrange + var console = new Mock(); + var repl = new Mock(); + + console.SetupProperty(x => x.ForegroundColor); + repl.Setup(x => x.FileSystem).Returns(new Mock().Object); + + var cmd = new CwdCommand(console.Object); + var expectedForegroundColor = console.Object.ForegroundColor; + + // act + cmd.Execute(repl.Object, null); + + // assert + Assert.Equal(expectedForegroundColor, console.Object.ForegroundColor); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/ExitCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/ExitCommandTests.cs new file mode 100644 index 00000000..f4e77b88 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/ExitCommandTests.cs @@ -0,0 +1,78 @@ +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class ExitCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsExit() + { + // act + var cmd = new ExitCommand(new Mock().Object); + + // assert + Assert.Equal("exit", cmd.CommandName); + } + } + + public class ExecuteMethod + { + private const string message = "Are you sure you wish to exit? (y/n):"; + + [Fact] + public void PromptsUserBeforeExiting() + { + // arrange + var console = new Mock(); + console.Setup(x => x.ReadLine(message)).Returns("n"); + var executor = new Mock(); + var cmd = new ExitCommand(console.Object); + + // act + cmd.Execute(executor.Object, null); + + // assert + console.Verify(x => x.ReadLine(message)); + } + + [Fact] + public void ExitsWhenUserAnswersYes() + { + // arrange + var console = new Mock(); + console.Setup(x => x.ReadLine(message)).Returns("y"); + + var executor = new Mock(); + var cmd = new ExitCommand(console.Object); + + // act + cmd.Execute(executor.Object, null); + + // assert + executor.Verify(x => x.Terminate()); + } + + [Fact] + public void DoesNotExitWhenUserAnswersNo() + { + // arrange + var console = new Mock(); + console.Setup(x => x.ReadLine(message)).Returns("n"); + + var executor = new Mock(); + var cmd = new ExitCommand(console.Object); + + // act + cmd.Execute(executor.Object, null); + + // assert + executor.Verify(x => x.Terminate(), Times.Never); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/ReplCommands/HelpCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/HelpCommandTests.cs new file mode 100644 index 00000000..2cbbecf2 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/HelpCommandTests.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class HelpCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsHelp() + { + // act + var cmd = new HelpCommand(new Mock().Object); + + // assert + Assert.Equal("help", cmd.CommandName); + } + } + + public class ExecuteMethod + { + [Fact] + public void PrintsCommandsToConsole() + { + // arrange + var console = new Mock(); + console.Setup(c => c.Width).Returns(80); + + var repl = new Mock(); + var clearCommand = new ClearCommand(console.Object); + var exitCommand = new ExitCommand(console.Object); + + var commands = new Dictionary + { + {clearCommand.CommandName, clearCommand}, + {exitCommand.CommandName, exitCommand}, + }; + + repl.Setup(x => x.Commands).Returns(commands); + + var cmd = new HelpCommand(console.Object); + + // act + cmd.Execute(repl.Object, null); + + // assert + console.Verify(x => x.WriteLine(It.IsAny()), Times.Exactly(4)); + console.Verify(x => x.WriteLine(It.Is(f => f.StartsWith(" :" + clearCommand.CommandName) && f.Contains(clearCommand.Description))), Times.Once); + console.Verify(x => x.WriteLine(It.Is(f => f.StartsWith(" :" + exitCommand.CommandName) && f.Contains(exitCommand.Description))), Times.Once); + } + + [Fact] + public void CorrectlyPrintsCommandsToConsoleAfterAlias() + { + // arrange + var console = new Mock(); + console.Setup(c => c.Width).Returns(80); + + var repl = new Mock(); + var clearCommand = new ClearCommand(console.Object); + var aliasCommand = new AliasCommand(console.Object); + + var commands = new Dictionary + { + {clearCommand.CommandName, clearCommand}, + {aliasCommand.CommandName, aliasCommand}, + }; + + repl.Setup(x => x.Commands).Returns(commands); + var cmd = new HelpCommand(console.Object); + + aliasCommand.Execute(repl.Object, new[] { "clear", "clr" }); + + // act + cmd.Execute(repl.Object, null); + + // assert + // because we now have formatted wrapping with the description, we have to remove + // all the extra spaces before verifying + console.Verify(x => x.WriteLine(It.Is(f => f.StartsWith(" :" + clearCommand.CommandName) && f.Replace(" ", " ").Contains(clearCommand.Description))), Times.Once); + console.Verify(x => x.WriteLine(It.Is(f => f.StartsWith(" :" + aliasCommand.CommandName) && f.Replace(" ", " ").Contains(aliasCommand.Description))), Times.Once); + console.Verify(x => x.WriteLine(It.Is(f => f.StartsWith(" :clr") && f.Replace(" ", " ").Contains(clearCommand.Description))), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/InstallCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/InstallCommandTests.cs new file mode 100644 index 00000000..5e1322a4 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/InstallCommandTests.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Versioning; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; +using Xunit.Extensions; + +namespace ScriptCs.Tests.ReplCommands +{ + public class InstallCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsInstall() + { + // act + var cmd = new InstallCommand( + new Mock().Object, + new Mock().Object, + new TestLogProvider(), + new Mock().Object); + + // assert + Assert.Equal("install", cmd.CommandName); + } + } + + public class ExecuteMethod + { + private readonly Mock _packageInstaller; + private readonly Mock _packageAssemblyResolver; + private readonly TestLogProvider _logProvider = new TestLogProvider(); + private readonly Mock _installationProvider; + private readonly Mock _repl; + + public ExecuteMethod() + { + _packageInstaller = new Mock(); + _packageAssemblyResolver = new Mock(); + _installationProvider = new Mock(); + _repl = new Mock(); + + var fs = new Mock(); + fs.Setup(x => x.CurrentDirectory).Returns(@"c:\dir"); + + _repl.SetupGet(x => x.FileSystem).Returns(fs.Object); + _repl.SetupGet(x => x.References).Returns(new AssemblyReferences()); + } + + private InstallCommand GetCommand() + { + return new InstallCommand(_packageInstaller.Object, _packageAssemblyResolver.Object, _logProvider, _installationProvider.Object); + } + + [Fact] + public void IfArgsAreEmptyReturns() + { + // arrange + var cmd = GetCommand(); + + // act + var result = cmd.Execute(_repl.Object, null); + + // assert + Assert.Null(result); + } + + [Fact] + public void InstallsPackageUsingArg0AsName() + { + // arrange + var cmd = GetCommand(); + + // act + cmd.Execute(_repl.Object, new[] { "scriptcs" }); + + // assert + _packageInstaller.Verify( + x => x.InstallPackages( + It.Is>(i => i.ElementAt(0).PackageId == "scriptcs"), false), + Times.Once); + } + + [Fact] + public void InstallsPackageUsingArg1AsVersion() + { + // arrange + var cmd = GetCommand(); + + // act + cmd.Execute(_repl.Object, new[] { "scriptcs", "0.9" }); + + // assert + var packageRef = new PackageReference("scriptcs", new FrameworkName(".NETFramework,Version=v4.0"), "0.9"); + _packageInstaller.Verify( + x => x.InstallPackages( + It.Is>(i => i.ElementAt(0).Version == packageRef.Version), + false), + Times.Once); + } + + [Theory] + [InlineData("pre")] + [InlineData("Pre")] + public void InstallsPackageUsingArg2AsPreReleaseFlag(string preReleaseFlag) + { + // arrange + var cmd = GetCommand(); + + // act + cmd.Execute(_repl.Object, new[] { "scriptcs", "0.9", preReleaseFlag }); + + // assert + _packageInstaller.Verify( + x => x.InstallPackages(It.IsAny>(), true), Times.Once); + } + + [Fact] + public void AfterInstallationSavesPackages() + { + // arrange + var cmd = GetCommand(); + + // act + cmd.Execute(_repl.Object, new[] { "scriptcs" }); + + // assert + _packageAssemblyResolver.Verify(x => x.SavePackages(), Times.Once); + } + + [Fact] + public void AddsAssemlbyReferenceToRepl() + { + // arrange + var dummyAssemblies = new[] { "assembly1.dll", "assembly2.dll" }; + _packageAssemblyResolver.Setup(x => x.GetAssemblyNames(@"c:\dir")).Returns(dummyAssemblies); + + var cmd = GetCommand(); + + // act + cmd.Execute(_repl.Object, new[] { "scriptcs" }); + + // assert + _repl.Verify(x => x.AddReferences(dummyAssemblies), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/ReferencesCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/ReferencesCommandTests.cs new file mode 100644 index 00000000..ec591616 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/ReferencesCommandTests.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Should; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class ReferencesCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsReferences() + { + // act + var cmd = new ReferencesCommand(); + + // assert + cmd.CommandName.ShouldEqual("references"); + } + } + + public class ExecuteMethod + { + private readonly Mock _repl; + + public ExecuteMethod() + { + _repl = new Mock(); + } + + [Fact] + public void ShouldReturnAssembliesFromExecutor() + { + var assemblies = new AssemblyReferences( + new List { typeof(string).Assembly }, new List { "path1", "path2" }); + + _repl.SetupGet(x => x.References).Returns(assemblies); + + var cmd = new ReferencesCommand(); + var result = cmd.Execute(_repl.Object, null); + + var expected = new List { typeof(string).Assembly.FullName, "path1", "path2" }; + + ((IEnumerable)result).ToList().ShouldEqual(expected); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/ReplCommands/ResetCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/ResetCommandTests.cs new file mode 100644 index 00000000..97eb4f6c --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/ResetCommandTests.cs @@ -0,0 +1,41 @@ +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class ResetCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsReset() + { + // act + var cmd = new ResetCommand(); + + // assert + Assert.Equal("reset", cmd.CommandName); + } + } + + public class ExecuteMethod + { + [Fact] + public void CallsReplReset() + { + // arrange + var repl = new Mock(); + + var cmd = new ResetCommand(); + + // act + cmd.Execute(repl.Object, null); + + // assert + repl.Verify(x => x.Reset(), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/ScriptPacksCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/ScriptPacksCommandTests.cs new file mode 100644 index 00000000..f46e74ee --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/ScriptPacksCommandTests.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class ScriptPacksCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsScriptPacks() + { + // act + var cmd = new ScriptPacksCommand(new Mock().Object); + + // assert + Assert.Equal("scriptpacks", cmd.CommandName); + } + } + + public class ExecuteMethod + { + private Mock _console; + private Mock _repl; + private Mock _scriptPackSession; + + public ExecuteMethod() + { + _console = new Mock(); + _repl = new Mock(); + _scriptPackSession = new Mock(Enumerable.Empty(), new string[0]); + + _scriptPackSession.Setup(x => x.Contexts).Returns(new List { new DummyScriptPack() }); + _repl.Setup(x => x.ScriptPackSession).Returns(_scriptPackSession.Object); + _repl.Setup(x => x.Namespaces).Returns(new Collection()); + } + + [Fact] + public void ShouldExitIfThereAreNoScriptPacks() + { + _scriptPackSession.Setup(x => x.Contexts).Returns((IEnumerable)null); + + var cmd = new ScriptPacksCommand(_console.Object); + cmd.Execute(_repl.Object, null); + + _console.Verify(x => x.WriteLine("There are no script packs available in this REPL session")); + } + + [Fact] + public void ShouldPrintMethodSignatures() + { + var cmd = new ScriptPacksCommand(_console.Object); + + cmd.Execute(_repl.Object, null); + + _console.Verify(x => x.WriteLine(typeof(DummyScriptPack).FullName.ToString())); + _console.Verify(x => x.WriteLine("** Methods **")); + _console.Verify(x => x.WriteLine(" - string Foo(int bar)")); + _console.Verify(x => x.WriteLine(" - ScriptCs.Tests.ReplCommands.DummyScriptPack Something()")); + } + + [Fact] + public void ShouldPrintPropertyInformation() + { + var cmd = new ScriptPacksCommand(_console.Object); + + cmd.Execute(_repl.Object, null); + + _console.Verify(x => x.WriteLine(typeof(DummyScriptPack).FullName.ToString())); + _console.Verify(x => x.WriteLine("** Properties **")); + _console.Verify(x => x.WriteLine(" - double FooBar { get; set; }")); + _console.Verify(x => x.WriteLine(" - ScriptCs.Tests.ReplCommands.DummyScriptPack Xyz { get; }")); + } + + [Fact] + public void ShouldRespectNamespaces() + { + _repl.Setup(x => x.Namespaces).Returns(new Collection { "ScriptCs.Tests.ReplCommands" }); + var cmd = new ScriptPacksCommand(_console.Object); + + cmd.Execute(_repl.Object, null); + + _console.Verify(x => x.WriteLine(" - DummyScriptPack Something()")); + _console.Verify(x => x.WriteLine(" - DummyScriptPack Xyz { get; }")); + } + } + } + + public class DummyScriptPack : IScriptPackContext + { + public string Foo(int bar) + { + return bar.ToString(); + } + + public DummyScriptPack Something() + { + return null; + } + + public double FooBar { get; set; } + + public DummyScriptPack Xyz + { + get { return null; } + } + } + + internal static class DummyScriptPackExtensions + { + public static void FooExtension(this DummyScriptPack dummyScriptPack) + { + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/UsingsCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/UsingsCommandTests.cs new file mode 100644 index 00000000..b884dd4c --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/UsingsCommandTests.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Should; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class UsingsCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsUsings() + { + // act + var cmd = new UsingsCommand(); + + // assert + cmd.CommandName.ShouldEqual("usings"); + } + } + + public class ExecuteMethod + { + private Mock _repl; + + public ExecuteMethod() + { + _repl = new Mock(); + } + + [Fact] + public void ShouldReturnNamespacesFromExecutor() + { + var ns = new List {"System"}; + _repl.SetupGet(x => x.Namespaces).Returns(ns); + + var cmd = new UsingsCommand(); + var result = cmd.Execute(_repl.Object, null); + + result.ShouldBeSameAs(ns); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplCommands/VarsCommandTests.cs b/test/ScriptCs.Core.Tests/ReplCommands/VarsCommandTests.cs new file mode 100644 index 00000000..bccf517c --- /dev/null +++ b/test/ScriptCs.Core.Tests/ReplCommands/VarsCommandTests.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.ReplCommands; +using Should; +using Xunit; + +namespace ScriptCs.Tests.ReplCommands +{ + public class VarsCommandTests + { + public class CommandNameProperty + { + [Fact] + public void ReturnsUsings() + { + // act + var cmd = new VarsCommand(); + + // assert + cmd.CommandName.ShouldEqual("vars"); + } + } + + public class ExecuteMethod + { + private Mock _repl; + + public ExecuteMethod() + { + _repl = new Mock(); + } + + [Fact] + public void ShouldReturnLocalVarsFromEngine() + { + var locals = new List {"int x = 0"}; + var replEngine = new Mock(); + replEngine.Setup(x => x.GetLocalVariables(It.IsAny())).Returns(locals); + _repl.SetupGet(x => x.ScriptEngine).Returns(replEngine.Object); + + var cmd = new VarsCommand(); + var result = cmd.Execute(_repl.Object, null); + + result.ShouldBeSameAs(locals); + } + + [Fact] + public void ShouldReturnNullForEngineWhichisNotReplEngine() + { + var replEngine = new Mock(); + _repl.SetupGet(x => x.ScriptEngine).Returns(replEngine.Object); + + var cmd = new VarsCommand(); + var result = cmd.Execute(_repl.Object, null); + + result.ShouldBeNull(); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ReplTests.cs b/test/ScriptCs.Core.Tests/ReplTests.cs index 1f91ccca..8c8422b4 100644 --- a/test/ScriptCs.Core.Tests/ReplTests.cs +++ b/test/ScriptCs.Core.Tests/ReplTests.cs @@ -2,13 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Common.Logging; using Moq; +using Moq.Protected; using ScriptCs.Contracts; using Should; using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -19,12 +18,19 @@ public class Mocks public Mocks() { FileSystem = new Mock(); + FileSystem.SetupGet(x => x.CurrentDirectory).Returns(""); + FileSystem.SetupGet(x => x.BinFolder).Returns("bin"); + FileSystem.SetupGet(x => x.DllCacheFolder).Returns(".cache"); + FileSystem.SetupGet(x => x.PackagesFolder).Returns("scriptcs_packages"); ScriptEngine = new Mock(); - Logger = new Mock(); + LogProvider = new TestLogProvider(); + ScriptLibraryComposer = new Mock(); + ScriptLibraryComposer.SetupGet(p => p.ScriptLibrariesFile).Returns("PackageScripts.csx"); Console = new Mock(); ScriptPack = new Mock(); FilePreProcessor = new Mock(); ObjectSerializer = new Mock(); + ReplCommands = new[] { new Mock() }; } public Mock ObjectSerializer { get; private set; } @@ -33,18 +39,33 @@ public Mocks() public Mock ScriptEngine { get; private set; } - public Mock Logger { get; private set; } + public TestLogProvider LogProvider { get; private set; } public Mock Console { get; private set; } public Mock ScriptPack { get; private set; } public Mock FilePreProcessor { get; private set; } + + public Mock ScriptLibraryComposer { get; private set; } + + public Mock[] ReplCommands { get; set; } } public static Repl GetRepl(Mocks mocks) { - return new Repl(new string[0], mocks.FileSystem.Object, mocks.ScriptEngine.Object, mocks.ObjectSerializer.Object, mocks.Logger.Object, mocks.Console.Object, mocks.FilePreProcessor.Object); + return new Repl( + new string[0], + mocks.FileSystem.Object, + mocks.ScriptEngine.Object, + mocks.ObjectSerializer.Object, + mocks.LogProvider, + mocks.ScriptLibraryComposer.Object, + mocks.Console.Object, + mocks.FilePreProcessor.Object, + mocks.ReplCommands.Select(x => x.Object), + new Printers(mocks.ObjectSerializer.Object), + new ScriptInfo()); } public class TheConstructor @@ -56,7 +77,6 @@ public void ShouldProperlyInitializeMembers() var repl = GetRepl(mocks); repl.FileSystem.ShouldEqual(mocks.FileSystem.Object); repl.ScriptEngine.ShouldEqual(mocks.ScriptEngine.Object); - repl.Logger.ShouldEqual(mocks.Logger.Object); repl.Console.ShouldEqual(mocks.Console.Object); } } @@ -65,13 +85,16 @@ public class TheInitializeMethod { private Mocks _mocks; private Repl _repl; + private string _tempPath; public TheInitializeMethod() { + _tempPath = Path.GetTempPath(); + _mocks = new Mocks(); _repl = GetRepl(_mocks); - _mocks.FileSystem.Setup(x => x.CurrentDirectory).Returns(@"c:\"); - var paths = new[] { @"c:\path" }; + _mocks.FileSystem.Setup(x => x.CurrentDirectory).Returns(_tempPath); + var paths = new[] { Path.Combine(_tempPath, "path") }; _repl.Initialize(paths, new[] { _mocks.ScriptPack.Object }); } @@ -80,16 +103,16 @@ public void PopulatesReferences() { foreach (var reference in Repl.DefaultReferences) { - _repl.References.PathReferences.ShouldContain(reference); + _repl.References.Paths.ShouldContain(reference); } - _repl.References.PathReferences.ShouldContain(@"c:\path"); + _repl.References.Paths.ShouldContain(Path.Combine(_tempPath, "path")); } [Fact] public void SetsTheScriptEngineBaseDirectory() { - _mocks.ScriptEngine.VerifySet(x => x.BaseDirectory = @"c:\bin"); + _mocks.ScriptEngine.VerifySet(x => x.BaseDirectory = Path.Combine(_tempPath, "bin")); } [Fact] @@ -144,6 +167,7 @@ public TheExecuteMethod() .Returns(new FilePreProcessorResult { Code = "foo" }); _repl = GetRepl(_mocks); _repl.Console.ForegroundColor = ConsoleColor.White; + _repl.Initialize(Enumerable.Empty(), Enumerable.Empty(), String.Empty); _repl.Execute("foo"); } @@ -162,15 +186,129 @@ public void ResetsColorAfterExecutingScript() [Fact] public void CallsExecuteOnTheScriptEngine() { - _mocks.ScriptEngine.Verify(x => x.Execute("foo", new string[0], _repl.References, Repl.DefaultNamespaces, It.IsAny())); + _mocks.ScriptEngine.Verify( + x => x.Execute( + "Env.Initialize();" + Environment.NewLine + "foo", + new string[0], + It.Is(i => i.Assemblies.SequenceEqual(_repl.References.Assemblies)), + It.Is>(i => i.SequenceEqual(_repl.Namespaces)), + It.IsAny())); + } + + [Fact] + public void ShouldPassExtraNameSpacesToEngineIfFilePreProcessorProducesThem() + { + _mocks.FilePreProcessor.Setup(x => x.ProcessScript(It.Is(i => i == "#load foo.csx"))) + .Returns(new FilePreProcessorResult { Namespaces = new List { "Foo", "Bar" } }); + + _repl.Execute("#load foo.csx"); + + _mocks.ScriptEngine.Verify(x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.Is>(i => i.SequenceEqual(_repl.Namespaces)), + It.IsAny())); + + _repl.Namespaces.Count().ShouldEqual(ScriptExecutor.DefaultNamespaces.Count() + 2); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddNamespacesFromScriptLibrary( + [Frozen] Mock scriptEngine, + Mock repl + ) + { + repl.Protected(); + repl.Setup(r => r.InjectScriptLibraries(It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback((string p, FilePreProcessorResult r, IDictionary s) => + { + r.Namespaces.Add("Foo.Bar"); + }); + + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); + + repl.Object.Initialize(Enumerable.Empty(), Enumerable.Empty()); + repl.Object.Execute("", new string[] {}); + + scriptEngine.Verify( + e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.Is>(x => x.Contains("Foo.Bar")), + It.IsAny()), + Times.Once()); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddReferencesFromScriptLibrary( + [Frozen] Mock scriptEngine, + Mock repl + ) + { + repl.Protected(); + repl.Setup(r => r.InjectScriptLibraries(It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback((string p, FilePreProcessorResult r, IDictionary s) => + { + r.References.Add("Foo.Bar"); + }); + + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); + + + repl.Object.Initialize(Enumerable.Empty(), Enumerable.Empty()); + repl.Object.Execute("", new string[] { }); + + scriptEngine.Verify( + e => e.Execute( + It.IsAny(), + It.IsAny(), + It.Is(x => x.Paths.Contains("Foo.Bar")), + It.IsAny>(), + It.IsAny()), + Times.Once()); + } + + [Fact] + public void ShouldRemoveInvalidNamespacesIfScriptResultContainsany() + { + _mocks.FilePreProcessor.Setup(x => x.ProcessScript(It.Is(i => i == "#load foo.csx"))) + .Returns(new FilePreProcessorResult { Namespaces = new List { "Foo", "Bar" } }); + _mocks.ScriptEngine.Setup( + x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())).Returns(new ScriptResult(invalidNamespaces: new string[] { "Foo" })); + + _repl.Execute("#load foo.csx"); + _repl.Namespaces.Count().ShouldEqual(ScriptExecutor.DefaultNamespaces.Count() + 1); + _repl.Namespaces.ShouldNotContain("Foo"); } [Fact] public void CatchesExceptionsAndWritesThemInRed() { _mocks.ScriptEngine.Setup( - x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) - .Throws(); + x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(); _repl.Execute("foo"); @@ -203,11 +341,18 @@ public void ShouldExecuteLoadedFileIfLineIsALoad() mocks.FilePreProcessor.Setup(x => x.ProcessScript(It.IsAny())) .Returns(new FilePreProcessorResult()); mocks.FileSystem.Setup(x => x.FileExists("file.csx")).Returns(true); - _repl = GetRepl(mocks); + _repl.Initialize(Enumerable.Empty(), Enumerable.Empty(), ""); _repl.Execute("#load \"file.csx\""); - mocks.ScriptEngine.Verify(i => i.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once()); + mocks.ScriptEngine.Verify( + i => i.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), + Times.Once()); } [Fact] @@ -219,7 +364,14 @@ public void ShouldNotExecuteLoadedFileIfFileDoesNotExist() _repl = GetRepl(mocks); _repl.Execute("#load \"file.csx\""); - mocks.ScriptEngine.Verify(i => i.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never()); + mocks.ScriptEngine.Verify( + i => i.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), + Times.Never()); } [Fact] @@ -236,8 +388,8 @@ public void ShouldReferenceAssemblyIfLineIsAReference() _repl.Initialize(Enumerable.Empty(), Enumerable.Empty()); _repl.Execute("#r \"my.dll\""); - //default references = 6, + 1 we just added - _repl.References.PathReferences.Count().ShouldEqual(7); + //default references = 10, + 1 we just added + _repl.References.Paths.Count().ShouldEqual(11); } [Fact] @@ -269,7 +421,7 @@ public void ShouldReferenceAssemblyBasedOnNameIfFileDoesNotExistBecauseItLooksIn _repl.Initialize(Enumerable.Empty(), Enumerable.Empty()); _repl.Execute("#r \"PresentationCore\""); - _repl.References.PathReferences.Contains("PresentationCore").ShouldBeTrue(); + _repl.References.Paths.Contains("PresentationCore").ShouldBeTrue(); } [Fact] @@ -286,7 +438,7 @@ public void ShouldReferenceAssemblyBasedOnNameWithExtensionIfFileDoesNotExistBec _repl.Initialize(Enumerable.Empty(), Enumerable.Empty()); _repl.Execute("#r \"my.dll\""); - _repl.References.PathReferences.Contains("my.dll").ShouldBeTrue(); + _repl.References.Paths.Contains("my.dll").ShouldBeTrue(); } [Fact] @@ -307,7 +459,7 @@ public void ShouldRemoveReferenceIfAssemblyIsNotFound() _repl = GetRepl(mocks); _repl.Initialize(Enumerable.Empty(), Enumerable.Empty()); _repl.Execute("#r \"my.dll\""); - _repl.References.PathReferences.Contains("my.dll").ShouldBeFalse(); + _repl.References.Paths.Contains("my.dll").ShouldBeFalse(); } [Fact] @@ -319,22 +471,30 @@ public void ShouldNotExecuteAnythingIfLineIsAReference() _repl.Initialize(Enumerable.Empty(), Enumerable.Empty()); _repl.Execute("#r \"my.dll\""); - mocks.ScriptEngine.Verify(i => i.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never()); + mocks.ScriptEngine.Verify( + i => i.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), + Times.Never()); } [Fact] - public void ShouldSetBufferIFLineIsFirstOfMultilineConstruct() + public void ShouldSetBufferIfLineIsFirstOfMultilineConstruct() { var mocks = new Mocks(); mocks.ScriptEngine.Setup( x => - x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny>(), It.IsAny())) - .Returns(x => new ScriptResult() - { - ExpectingClosingChar = ')', - IsPendingClosingChar = true - }); + x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(ScriptResult.Incomplete); + mocks.FilePreProcessor.Setup(x => x.ProcessScript(It.IsAny())) .Returns(new FilePreProcessorResult { Code = "var x = 1;" }); mocks.FileSystem.Setup(i => i.CurrentDirectory).Returns("C:/"); @@ -351,12 +511,13 @@ public void ShouldResetBufferIfLineIsNoLongerMultilineConstruct() var mocks = new Mocks(); mocks.ScriptEngine.Setup( x => - x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny>(), It.IsAny())) - .Returns(new ScriptResult - { - IsPendingClosingChar = false - }); + x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(ScriptResult.Empty); mocks.FilePreProcessor.Setup(x => x.ProcessScript(It.IsAny())) .Returns(new FilePreProcessorResult { Code = "}" }); mocks.FileSystem.Setup(i => i.CurrentDirectory).Returns("C:/"); @@ -372,14 +533,14 @@ public void ShouldResetBufferIfLineIsNoLongerMultilineConstruct() public void ShouldResubmitEverytingIfLineIsNoLongerMultilineConstruct() { var mocks = new Mocks(); - mocks.ScriptEngine.Setup( - x => - x.Execute(It.Is(i => i == "class test {}"), It.IsAny(), It.IsAny(), - It.IsAny>(), It.IsAny())) - .Returns(new ScriptResult - { - IsPendingClosingChar = false - }); + mocks.ScriptEngine.Setup(x => x.Execute( + It.Is(i => i == "class test {}"), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(ScriptResult.Empty); + mocks.FileSystem.Setup(i => i.CurrentDirectory).Returns("C:/"); _repl = GetRepl(mocks); _repl.Buffer = "class test {"; @@ -388,6 +549,203 @@ public void ShouldResubmitEverytingIfLineIsNoLongerMultilineConstruct() mocks.ScriptEngine.Verify(); } + + [Fact] + public void ShouldPickReplCommandIfLineStartsWithColon() + { + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + _repl = GetRepl(mocks); + + _repl.Execute(":hello", null); + + helloCommand.Verify(x => x.Execute(_repl, It.Is(i => i.Length == 0)), Times.Once); + } + + [Fact] + public void ShouldEvaluateArgs() + { + var dummyObject = new DummyClass { Hello = "World" }; + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ScriptEngine.Setup(x => x.Execute( + "myObj", + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(new ScriptResult(returnValue: dummyObject)); + + mocks.ScriptEngine.Setup(x => x.Execute( + "100", + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(new ScriptResult(returnValue: 100)); + + _repl = GetRepl(mocks); + + _repl.Execute(":hello 100 myObj", null); + + helloCommand.Verify( + x => x.Execute( + _repl, + It.Is(i => + i[0].GetType() == typeof(int) && + (int)i[0] == 100 && + i[1].Equals(dummyObject))), + Times.Once); + } + + [Fact] + public void ShouldEvaluateStrings() + { + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ScriptEngine.Setup(x => x.Execute( + "\"world\"", + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(new ScriptResult(returnValue: "world")); + + _repl = GetRepl(mocks); + + _repl.Execute(":hello \"world\"", null); + + helloCommand.Verify( + x => x.Execute( + _repl, + It.Is(i => + i[0].GetType() == typeof(string) && + (string)i[0] == "world")), + Times.Once); + } + + [Fact] + public void ShouldSurfaceArgumentCompilationErrors() + { + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ScriptEngine.Setup(x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(new ScriptResult(compilationException: new Exception())); + + _repl = GetRepl(mocks); + + var result = _repl.Execute(":hello foo", null); + + result.ExecuteExceptionInfo.SourceException.Message.ShouldContain( + "argument is not a valid expression: foo", StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void ShouldSurfaceArgumentExecutionErrors() + { + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ScriptEngine.Setup(x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(new ScriptResult(executionException: new Exception())); + + _repl = GetRepl(mocks); + + var result = _repl.Execute(":hello foo", null); + + result.ExecuteExceptionInfo.SourceException.Message.ShouldContain( + "argument is not a valid expression: foo", StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void ShouldSurfaceIncompleteArguments() + { + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ScriptEngine.Setup(x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(ScriptResult.Incomplete); + + _repl = GetRepl(mocks); + + var result = _repl.Execute(":hello foo", null); + + result.ExecuteExceptionInfo.SourceException.Message.ShouldContain( + "argument is not a valid expression: foo", StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void ShouldPrintTheReturnToConsoleIfCommandHasReturnValue() + { + object returnValue = new DummyClass { Hello = "World" }; + + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + helloCommand.Setup(x => x.Execute(It.IsAny(), It.IsAny())) + .Returns(returnValue); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ObjectSerializer.Setup(x => x.Serialize(returnValue)).Returns("hello world"); + + _repl = GetRepl(mocks); + + _repl.Execute(":hello", null); + + mocks.ObjectSerializer.Verify(x => x.Serialize(returnValue), Times.Once); + mocks.Console.Verify(x => x.WriteLine("hello world"), Times.Once); + } + + [Fact] + public void ShouldReturnCommandsScriptResultfCommandHasReturnValueThatAlreadyIsScriptResult() + { + var returnValue = new ScriptResult("hello world"); + + var helloCommand = new Mock(); + helloCommand.SetupGet(x => x.CommandName).Returns("hello"); + helloCommand.Setup(x => x.Execute(It.IsAny(), It.IsAny())) + .Returns(returnValue); + + var mocks = new Mocks { ReplCommands = new[] { helloCommand } }; + mocks.ObjectSerializer.Setup(x => x.Serialize(returnValue.ReturnValue)).Returns("hello world"); + + _repl = GetRepl(mocks); + + var result = _repl.Execute(":hello", null); + + mocks.ObjectSerializer.Verify(x => x.Serialize(returnValue.ReturnValue), Times.Once); + mocks.Console.Verify(x => x.WriteLine("hello world"), Times.Once); + result.ShouldBeSameAs(returnValue); + } + } + + private class DummyClass + { + public string Hello { get; set; } } } } diff --git a/test/ScriptCs.Core.Tests/ScriptCs.Core.Tests.csproj b/test/ScriptCs.Core.Tests/ScriptCs.Core.Tests.csproj index a2d3b769..322ee903 100644 --- a/test/ScriptCs.Core.Tests/ScriptCs.Core.Tests.csproj +++ b/test/ScriptCs.Core.Tests/ScriptCs.Core.Tests.csproj @@ -1,99 +1,27 @@ - - - + - {AC228213-7356-4F0D-BA48-EBA5FB8A7506} - Library - ScriptCs.Tests - ScriptCs.Core.Tests - ..\..\ - ..\..\ScriptCs.Test.ruleset + net461 - - False - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.dll - - - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.Configuration.dll - - - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - ..\..\packages\Microsoft.Web.Xdt.1.0.0\lib\net40\Microsoft.Web.XmlTransform.dll - - - ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - False - ..\..\packages\NuGet.Core.2.7.0\lib\net40-Client\NuGet.Core.dll - - - ..\..\packages\AutoFixture.3.6.5\lib\net40\Ploeh.AutoFixture.dll - - - ..\..\packages\AutoFixture.AutoMoq.3.6.5\lib\net40\Ploeh.AutoFixture.AutoMoq.dll - - - ..\..\packages\AutoFixture.Xunit.3.6.5\lib\net40\Ploeh.AutoFixture.Xunit.dll - - - - False - ..\..\packages\Should.1.1.20\lib\Should.dll - - - - - - ..\..\packages\xunit.1.9.1\lib\net20\xunit.dll - - - False - ..\..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll - + + + - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - ScriptCsAutoDataAttribute.cs - - - - - - - - - - - - - - - + + - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - {e590e710-e159-48e6-a3e6-1a83d3fe732c} - ScriptCs.Core - + - - + + + + + + + + - - \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/ScriptExecutorTests.cs b/test/ScriptCs.Core.Tests/ScriptExecutorTests.cs index 50635a05..7f79667d 100644 --- a/test/ScriptCs.Core.Tests/ScriptExecutorTests.cs +++ b/test/ScriptCs.Core.Tests/ScriptExecutorTests.cs @@ -1,14 +1,13 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using Moq; -using Ploeh.AutoFixture.Xunit; +using Moq.Protected; using ScriptCs.Contracts; using Should; using Xunit; -using Xunit.Extensions; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -17,303 +16,619 @@ public class ScriptExecutorTests public class TheInitializeMethod { [Theory, ScriptCsAutoData] - public void ShouldSetEngineBaseDirectoryBasedOnCurrentDirectoryAndBinFolder([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void ShouldSetEngineBaseDirectoryBasedOnCurrentDirectoryAndBinFolder( + [Frozen] Mock scriptEngine, + [Frozen] Mock fileSystem, + [Frozen] Mock preProcessor, + ScriptExecutor scriptExecutor) { // arrange preProcessor.Setup(x => x.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); - - var currentDirectory = @"C:\"; - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(currentDirectory); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(currentDirectory); - scriptEngine.SetupProperty(e => e.BaseDirectory); - var paths = new string[0]; - IEnumerable recipes = Enumerable.Empty(); - // act - scriptExecutor.Initialize(paths, recipes); + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); // assert - string expectedBaseDirectory = Path.Combine(currentDirectory, Constants.BinFolder); - expectedBaseDirectory.ShouldEqual(scriptEngine.Object.BaseDirectory); + scriptEngine.Object.BaseDirectory.ShouldEqual( + Path.Combine(fileSystem.Object.CurrentDirectory, "scriptcs_bin")); } [Theory, ScriptCsAutoData] - public void ShouldSetEngineDllCacheDirectoryBasedOnCurrentDirectory([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void ShouldSetEngineDllCacheDirectoryBasedOnCurrentDirectory( + [Frozen] Mock scriptEngine, + [Frozen] Mock fileSystem, + [Frozen] Mock preProcessor, + ScriptExecutor scriptExecutor) { // arrange preProcessor.Setup(x => x.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); - - var currentDirectory = @"C:\"; - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(currentDirectory); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(currentDirectory); - scriptEngine.SetupProperty(e => e.CacheDirectory); - var paths = new string[0]; - IEnumerable recipes = Enumerable.Empty(); - // act - scriptExecutor.Initialize(paths, recipes); + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); // assert - string expectedCacheDirectory = Path.Combine(currentDirectory, Constants.DllCacheFolder); - expectedCacheDirectory.ShouldEqual(scriptEngine.Object.CacheDirectory); + scriptEngine.Object.CacheDirectory.ShouldEqual( + Path.Combine(fileSystem.Object.CurrentDirectory, ".scriptcs_cache")); } [Theory, ScriptCsAutoData] - public void ShouldInitializeScriptPacks([Frozen] Mock preProcessor, [Frozen] Mock fileSystem, - [Frozen] Mock scriptPack1, ScriptExecutor scriptExecutor) + public void ShouldInitializeScriptPacks( + [Frozen] Mock preProcessor, + [Frozen] Mock scriptPack, + ScriptExecutor scriptExecutor) { - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(@"c:\my_script"); - fileSystem.Setup(f => f.CurrentDirectory).Returns(@"c:\my_script"); - - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + // arrange + preProcessor.Setup(p => p.ProcessFile(It.IsAny())) + .Returns(new FilePreProcessorResult { Code = "var a = 0;" }); - scriptPack1.Setup(p => p.Initialize(It.IsAny())); - scriptPack1.Setup(p => p.GetContext()).Returns(Mock.Of()); + scriptPack.Setup(p => p.Initialize(It.IsAny())); + scriptPack.Setup(p => p.GetContext()).Returns(Mock.Of()); // act - scriptExecutor.Initialize(Enumerable.Empty(), new[] { scriptPack1.Object }); + scriptExecutor.Initialize(Enumerable.Empty(), new[] { scriptPack.Object }); // assert - scriptPack1.Verify(p => p.Initialize(It.IsAny())); + scriptPack.Verify(p => p.Initialize(It.IsAny())); } } public class TheTerminateMethod { [Theory, ScriptCsAutoData] - public void ShouldTerminateScriptPacksWhenTerminateIsCalled([Frozen] Mock preProcessor, [Frozen] Mock fileSystem, - [Frozen] Mock scriptPack1, ScriptExecutor executor) + public void ShouldTerminateScriptPacksWhenTerminateIsCalled( + [Frozen] Mock preProcessor, + [Frozen] Mock scriptPack, + ScriptExecutor executor) { - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(@"c:\my_script"); - fileSystem.Setup(f => f.CurrentDirectory).Returns(@"c:\my_script"); + // arrange + preProcessor.Setup(p => p.ProcessFile(It.IsAny())) + .Returns(new FilePreProcessorResult { Code = "var a = 0;" }); - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + scriptPack.Setup(p => p.Initialize(It.IsAny())); + scriptPack.Setup(p => p.GetContext()).Returns(Mock.Of()); + scriptPack.Setup(p => p.Terminate()); - scriptPack1.Setup(p => p.Initialize(It.IsAny())); - scriptPack1.Setup(p => p.GetContext()).Returns(Mock.Of()); - scriptPack1.Setup(p => p.Terminate()); + executor.Initialize(Enumerable.Empty(), new[] { scriptPack.Object }); + executor.Execute("script.csx"); // act - executor.Initialize(Enumerable.Empty(), new[] { scriptPack1.Object }); - executor.Execute("script.csx"); executor.Terminate(); // assert - scriptPack1.Verify(p => p.Terminate()); + scriptPack.Verify(p => p.Terminate()); } } - public class TheExecuteMethod + public class TheEngineExecuteMethod { [Theory, ScriptCsAutoData] - public void ConstructsAbsolutePathBeforePreProcessingFile([Frozen] Mock preProcessor, [Frozen] Mock fileSystem, ScriptExecutor executor) + public void ShouldSetScriptInfo_ScriptPath( + [Frozen] Mock scriptEngine, + ScriptExecutor scriptExecutor) { - fileSystem.Setup(f => f.CurrentDirectory).Returns(@"c:\my_script"); - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(@"c:\my_script"); + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - executor.Execute("script.csx"); - preProcessor.Verify(p => p.ProcessFile(@"c:\my_script\script.csx")); + var result = new FilePreProcessorResult(); + result.ScriptPath = "Main.csx"; + scriptExecutor.EngineExecute("", new string[] {}, result); + scriptExecutor.ScriptInfo.ScriptPath.ShouldEqual("Main.csx"); } [Theory, ScriptCsAutoData] - public void DoNotChangePathIfAbsolute([Frozen] Mock preProcessor, [Frozen] Mock fileSystem, ScriptExecutor executor) + public void ShouldPopulateScriptInfo_LoadedScript( + [Frozen] Mock scriptEngine, + ScriptExecutor scriptExecutor) { - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(@"c:\my_script"); - fileSystem.Setup(f => f.CurrentDirectory).Returns(@"c:\my_script"); - - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); - executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - executor.Execute("script.csx"); + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - preProcessor.Verify(p => p.ProcessFile(@"c:\my_script\script.csx")); + var result = new FilePreProcessorResult(); + result.ScriptPath = "Main.csx"; + result.LoadedScripts.Add("Child.csx"); + scriptExecutor.EngineExecute("", new string[] { }, result); + scriptExecutor.ScriptInfo.LoadedScripts.First().ShouldEqual("Child.csx"); } [Theory, ScriptCsAutoData] - public void ShouldExecuteScriptReturnedFromFileProcessorInScriptEngineWhenExecuteIsInvoked([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void ShouldAddReferenceToEachDestinationFile( + [Frozen] Mock scriptEngine, + ScriptExecutor scriptExecutor) { - string code = Guid.NewGuid().ToString(); + // arrange + var defaultReferences = ScriptExecutor.DefaultReferences; var currentDirectory = @"C:\"; - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(currentDirectory); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(currentDirectory); + var destinationFilePath1 = Path.Combine(currentDirectory, @"bin\fileName1.cs"); + var destinationFilePath2 = Path.Combine(currentDirectory, @"bin\fileName2.cs"); + var destinationFilePath3 = Path.Combine(currentDirectory, @"bin\fileName3.cs"); + var destinationFilePath4 = Path.Combine(currentDirectory, @"bin\fileName4.cs"); - var scriptName = "script.csx"; - var paths = new string[0]; - var recipes = Enumerable.Empty(); + var destPaths = new[] + { + "System", + "System.Core", + destinationFilePath1, + destinationFilePath2, + destinationFilePath3, + destinationFilePath4, + }; + + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); - preProcessor.Setup(fs => fs.ProcessFile(Path.Combine(currentDirectory, scriptName))).Returns(new FilePreProcessorResult { Code = code }).Verifiable(); - scriptEngine.Setup(e => e.Execute(code, It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())); + scriptExecutor.AddReferences("a"); + scriptExecutor.AddReferences(new[] { "a", "a", "b", "c", "d" }); + scriptExecutor.AddReference(); + scriptExecutor.AddReferences(typeof(TheInitializeMethod)); + var explicitReferences = new[] + { + "a", + "b", + "c", + "d", + typeof(FactAttribute).Assembly.Location, + typeof(TheInitializeMethod).Assembly.Location, + }; + + scriptExecutor.Initialize(destPaths, Enumerable.Empty()); // act - scriptExecutor.Initialize(paths, recipes); - scriptExecutor.Execute(scriptName); + scriptExecutor.EngineExecute("", new string[] {}, new FilePreProcessorResult()); // assert - preProcessor.Verify(fs => fs.ProcessFile(Path.Combine(currentDirectory, scriptName)), Times.Once()); - - scriptEngine.Verify(s => s.Execute(code, It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once()); + var expectedReferencePaths = explicitReferences.Union(destPaths).Distinct(); + expectedReferencePaths.All(path => scriptExecutor.References.Paths.Contains(path)).ShouldBeTrue(); } [Theory, ScriptCsAutoData] - public void ShouldExecuteScriptWhenExecuteScriptIsInvoked([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void ShouldExecuteScriptReturnedFromFileProcessorInScriptEngine( + [Frozen] Mock scriptEngine, + ScriptExecutor scriptExecutor) { - string code = Guid.NewGuid().ToString(); + // arrange + var code = Guid.NewGuid().ToString(); - var currentDirectory = @"C:\"; - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(currentDirectory); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(currentDirectory); + scriptEngine.Setup(e => e.Execute( + code, It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); - var paths = new string[0]; - var recipes = Enumerable.Empty(); - var script = "var a=1;"; - preProcessor.Setup(fs => fs.ProcessScript(script)).Returns(new FilePreProcessorResult { Code = script }).Verifiable(); - scriptEngine.Setup(e => e.Execute(code, It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())); + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); // act - scriptExecutor.Initialize(paths, recipes); - scriptExecutor.ExecuteScript(script); + scriptExecutor.EngineExecute("", new string[] {}, new FilePreProcessorResult() {Code = code}); // assert - preProcessor.Verify(fs => fs.ProcessScript(script), Times.Once()); - scriptEngine.Verify(s => s.Execute(script, It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once()); + scriptEngine.Verify( + s => s.Execute( + "Env.Initialize();" + Environment.NewLine + code, + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), + Times.Once()); } [Theory, ScriptCsAutoData] - public void ShouldAddReferenceToEachDestinationFile([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void ShouldAddNamespacesFromScriptLibrary( + [Frozen] Mock scriptEngine, + Mock scriptExecutor + ) { - // arrange - var defaultReferences = ScriptExecutor.DefaultReferences; - preProcessor.Setup(x => x.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + scriptExecutor.Protected(); + var result = new FilePreProcessorResult(); + scriptExecutor.Setup(e => e.InjectScriptLibraries(It.IsAny(), result, It.IsAny>())) + .Callback((string p, FilePreProcessorResult r, IDictionary s) => + { + r.Namespaces.Add("Foo.Bar"); + }); + + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); + + + scriptExecutor.Object.Initialize(Enumerable.Empty(), Enumerable.Empty()); + scriptExecutor.Object.EngineExecute("", new string[] {}, result); + + scriptEngine.Verify( + e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.Is>(x => x.Contains("Foo.Bar")), + It.IsAny()), + Times.Once()); + } - var currentDirectory = @"C:\"; - var destinationFilePath1 = Path.Combine(currentDirectory, @"bin\fileName1.cs"); - var destinationFilePath2 = Path.Combine(currentDirectory, @"bin\fileName2.cs"); - var destinationFilePath3 = Path.Combine(currentDirectory, @"bin\fileName3.cs"); - var destinationFilePath4 = Path.Combine(currentDirectory, @"bin\fileName4.cs"); + [Theory, ScriptCsAutoData] + public void ShouldAddReferencesFromScriptLibrary( + [Frozen] Mock scriptEngine, + Mock scriptExecutor + ) + { + scriptExecutor.Protected(); + var result = new FilePreProcessorResult(); + scriptExecutor.Setup(e => e.InjectScriptLibraries(It.IsAny(), result, It.IsAny>())) + .Callback((string p, FilePreProcessorResult r, IDictionary s) => + { + r.References.Add("Foo.Bar"); + }); + + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); + + + scriptExecutor.Object.Initialize(Enumerable.Empty(), Enumerable.Empty()); + scriptExecutor.Object.EngineExecute("", new string[] {}, result); + + scriptEngine.Verify( + e => e.Execute( + It.IsAny(), + It.IsAny(), + It.Is(x => x.Paths.Contains("Foo.Bar")), + It.IsAny>(), + It.IsAny()), + Times.Once()); + } - var scriptName = "script.csx"; + [Theory, ScriptCsAutoData] + public void ShouldAddNamespaces( + [Frozen] Mock scriptEngine, + ScriptExecutor scriptExecutor) + { + scriptEngine.Setup(e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(currentDirectory); - fileSystem.Setup(fs => fs.GetWorkingDirectory(It.IsAny())).Returns(currentDirectory); + scriptExecutor.ImportNamespaces("a"); + scriptExecutor.ImportNamespaces(new[] { "a", "a", "b", "c", "d" }.ToArray()); + scriptExecutor.ImportNamespace(); + scriptExecutor.ImportNamespaces(typeof(TheInitializeMethod)); + var explicitNamespaces = new[] + { + "a", + "b", + "c", + "d", + typeof(FactAttribute).Namespace, + typeof(TheInitializeMethod).Namespace + }; - var destPaths = new string[] { "System", "System.Core", destinationFilePath1, destinationFilePath2, destinationFilePath3, destinationFilePath4 }; + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - scriptEngine.Setup(e => e.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())); + // act + scriptExecutor.EngineExecute("", new string[] {}, new FilePreProcessorResult()); - scriptExecutor.AddReferences("a"); - scriptExecutor.AddReferences(new[] { "a", "a", "b", "c", "d" }); - scriptExecutor.AddReference(); - scriptExecutor.AddReferences(typeof(TheInitializeMethod)); - var explicitReferences = new[] { "a", "b", "c", "d", typeof(FactAttribute).Assembly.Location, typeof(TheInitializeMethod).Assembly.Location }; + // assert + scriptEngine.Verify( + e => e.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.Is>(x => + x.SequenceEqual(ScriptExecutor.DefaultNamespaces.Union(explicitNamespaces))), + It.IsAny()), + Times.Once()); + } + + [Theory, ScriptCsAutoData] + public void ShouldPassDefaultNamespacesToEngine( + [Frozen] Mock engine, + ScriptExecutor executor) + { + // arrange + + executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); // act - scriptExecutor.Initialize(destPaths, Enumerable.Empty()); - scriptExecutor.Execute(scriptName); + executor.EngineExecute("", new string[] {}, new FilePreProcessorResult()); // assert - scriptEngine.Verify(e => e.Execute(It.IsAny(), It.IsAny(), It.Is(x => x.PathReferences.SequenceEqual(defaultReferences.Union(explicitReferences.Union(destPaths)))), It.IsAny>(), It.IsAny()), Times.Once()); + engine.Verify( + i => i.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.Is>(x => !x.Except(ScriptExecutor.DefaultNamespaces).Any()), + It.IsAny()), + Times.Exactly(1)); } [Theory, ScriptCsAutoData] - public void ShouldSetEngineFileName([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void ShouldPassDefaultReferencesToEngine( + [Frozen] Mock engine, + ScriptExecutor executor) { // arrange - const string CurrentDirectory = @"c:\scriptcs"; + + executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - scriptEngine.SetupProperty(e => e.FileName); + // act + executor.EngineExecute("", new string[] {}, new FilePreProcessorResult()); - fileSystem.Setup(fs => fs.GetWorkingDirectory(It.IsAny())).Returns(CurrentDirectory); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(CurrentDirectory); + // assert + engine.Verify( + i => i.Execute( + It.IsAny(), + It.IsAny(), + It.Is(x => + !x.Paths.Except(ScriptExecutor.DefaultReferences).Any()), + It.IsAny>(), + It.IsAny()), + Times.Exactly(1)); + } - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + [Theory, ScriptCsAutoData] + public void ShouldInvokeInjectScriptLibraries(Mock executor) + { + executor.Protected(); + executor.Object.Initialize(Enumerable.Empty(), Enumerable.Empty(), ""); + executor.Setup(e => e.InjectScriptLibraries( + It.IsAny(), It.IsAny(), It.IsAny>())); + + var result = new FilePreProcessorResult(); + + executor.Object.EngineExecute("", new string[] { }, result); + + executor.Verify(e => e.InjectScriptLibraries( + It.IsAny(), result, It.IsAny>())); + } + } - const string ScriptName = "script.csx"; + public class TheExecuteMethod + { + private readonly string _tempPath; + + public TheExecuteMethod() + { + _tempPath = Path.GetTempPath(); + } + + [Theory, ScriptCsAutoData] + public void ShouldInvokeEngineExecute( + [Frozen] Mock preProcessor, + Mock executor, + FilePreProcessorResult result + ) + { + executor.Protected(); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(result); + executor.Setup( + e => e.EngineExecute( + It.IsAny(), + It.IsAny(), + It.IsAny())); + + var args = new string[] {}; + + executor.Object.Execute("", args); + + executor.Verify( + e => e.EngineExecute( + "", + args, + result)); + } + + [Theory, ScriptCsAutoData] + public void ConstructsAbsolutePathBeforePreProcessingFile( + [Frozen] Mock preProcessor, + [Frozen] Mock fileSystem, + ScriptExecutor executor) + { + // arrange + fileSystem.Setup(f => f.CurrentDirectory).Returns(Path.Combine(_tempPath, "my_script")); + fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())) + .Returns(Path.Combine(_tempPath, "my_script")); + + preProcessor.Setup(p => p.ProcessFile(It.IsAny())) + .Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + + executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); // act - scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - scriptExecutor.Execute(ScriptName); + executor.Execute("script.csx"); // assert - scriptEngine.Object.FileName.ShouldEqual(ScriptName); + preProcessor.Verify(p => p.ProcessFile( + Path.Combine(_tempPath, "my_script", "script.csx"))); } [Theory, ScriptCsAutoData] - public void ShouldAddNamespaces([Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor scriptExecutor) + public void DoNotChangePathIfAbsolute( + [Frozen] Mock preProcessor, + [Frozen] Mock fileSystem, + ScriptExecutor executor) { // arrange - preProcessor.Setup(x => x.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())) + .Returns(Path.Combine(_tempPath, "my_script")); - var currentDirectory = @"C:\"; + fileSystem.Setup(f => f.CurrentDirectory).Returns(Path.Combine(_tempPath, "my_script")); - var scriptName = "script.csx"; + preProcessor.Setup(p => p.ProcessFile(It.IsAny())) + .Returns(new FilePreProcessorResult { Code = "var a = 0;" }); - fileSystem.Setup(fs => fs.CurrentDirectory).Returns(currentDirectory); - fileSystem.Setup(fs => fs.GetWorkingDirectory(It.IsAny())).Returns(currentDirectory); + executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - scriptEngine.Setup(e => e.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())); + // act + executor.Execute("script.csx"); - scriptExecutor.ImportNamespaces("a"); - scriptExecutor.ImportNamespaces(new[] { "a", "a", "b", "c", "d" }.ToArray()); - scriptExecutor.ImportNamespace(); - scriptExecutor.ImportNamespaces(typeof(TheInitializeMethod)); - var explicitNamespaces = new[] { "a", "b", "c", "d", typeof(FactAttribute).Namespace, typeof(TheInitializeMethod).Namespace }; + // assert + preProcessor.Verify(p => p.ProcessFile( + Path.Combine(_tempPath, "my_script", "script.csx"))); + } + + [Theory, ScriptCsAutoData] + public void ShouldSetEngineFileName( + [Frozen] Mock scriptEngine, + [Frozen] Mock preProcessor, + ScriptExecutor scriptExecutor) + { + // arrange + scriptEngine.SetupProperty(e => e.FileName); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + scriptExecutor.Initialize(Enumerable.Empty(), Enumerable.Empty()); + var scriptName = "script.csx"; // act - scriptExecutor.Initialize(new string[0], Enumerable.Empty()); scriptExecutor.Execute(scriptName); // assert - scriptEngine.Verify(e => e.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.Is>(x => x.SequenceEqual(ScriptExecutor.DefaultNamespaces.Union(explicitNamespaces))), It.IsAny()), Times.Once()); + scriptEngine.Object.FileName.ShouldEqual(scriptName); } + } + public class TheExecuteScriptMethod + { [Theory, ScriptCsAutoData] - public void ExecutorShouldPassDefaultNamespacesToEngine([Frozen] Mock engine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor executor) + public void ShouldInvokeEngineExecute( + [Frozen] Mock preProcessor, + Mock executor, + FilePreProcessorResult result + ) { - var expectedNamespaces = ScriptExecutor.DefaultNamespaces; + executor.Protected(); + preProcessor.Setup(p => p.ProcessScript(It.IsAny())).Returns(result); + executor.Setup( + e => e.EngineExecute( + It.IsAny(), + It.IsAny(), + It.IsAny())); + + var args = new string[] { }; + + executor.Object.ExecuteScript("", args); + + executor.Verify( + e => e.EngineExecute( + "workingdirectory", + args, + result)); + } - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(@"c:\my_script"); - fileSystem.Setup(f => f.CurrentDirectory).Returns(@"c:\my_script"); + } - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + public class TheInjectScriptLibrariesMethod + { + private IDictionary _state = new Dictionary(); + private FilePreProcessorResult _result = new FilePreProcessorResult(); + private FilePreProcessorResult _scriptLibrariesPreProcessorResult = new FilePreProcessorResult(); - executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - executor.Execute("script.csx"); + public TheInjectScriptLibrariesMethod() + { + _scriptLibrariesPreProcessorResult.Code = "Test"; + _result.Code = ""; + } - engine.Verify(i => i.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.Is>(x => !x.Except(expectedNamespaces).Any()), It.IsAny()), Times.Exactly(1)); + [Theory, ScriptCsAutoData] + public void ShouldExitIfPreProcessorResultIsNull(ScriptExecutor executor) + { + executor.InjectScriptLibraries("", _result, _state); + _result.Code.ShouldEqual("Env.Initialize();" + Environment.NewLine); } [Theory, ScriptCsAutoData] - public void ExecutorShouldPassDefaultReferencesToEngine([Frozen] Mock engine, [Frozen] Mock fileSystem, - [Frozen] Mock preProcessor, ScriptExecutor executor) + public void ShouldExitIfSessionPackageScriptsInjectedIsSet(Mock executor) { - var defaultReferences = ScriptExecutor.DefaultReferences; + _state["ScriptLibrariesInjected"] = null; + executor.Protected(); + executor.Setup(e => e.LoadScriptLibraries(It.IsAny())).Returns(_scriptLibrariesPreProcessorResult); + executor.Object.InjectScriptLibraries("", _result, _state); + _result.Code.ShouldBeEmpty(); + } - fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns(@"c:\my_script"); - fileSystem.Setup(f => f.CurrentDirectory).Returns(@"c:\my_script"); + [Theory, ScriptCsAutoData] + public void ShouldInjectResultCode(Mock executor) + { + executor.Protected(); + executor.Setup(e => e.LoadScriptLibraries(It.IsAny())).Returns(_scriptLibrariesPreProcessorResult); + executor.Object.InjectScriptLibraries("", _result, _state); + _result.Code.ShouldEqual("Test" + Environment.NewLine + "Env.Initialize();" + Environment.NewLine); + } - preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "var a = 0;" }); + [Theory, ScriptCsAutoData] + public void ShouldAddResultReferences(Mock executor) + { + executor.Protected(); + executor.Setup(e => e.LoadScriptLibraries(It.IsAny())).Returns(_scriptLibrariesPreProcessorResult); + _scriptLibrariesPreProcessorResult.References.Add("ref1"); + executor.Object.InjectScriptLibraries("", _result, _state); + _result.References.ShouldContain("ref1"); + } - executor.Initialize(Enumerable.Empty(), Enumerable.Empty()); - executor.Execute("script.csx"); + [Theory, ScriptCsAutoData] + public void ShouldAddResultNamespaces(Mock executor) + { + executor.Protected(); + executor.Setup(e => e.LoadScriptLibraries(It.IsAny())).Returns(_scriptLibrariesPreProcessorResult); + _scriptLibrariesPreProcessorResult.Namespaces.Add("ns1"); + executor.Object.InjectScriptLibraries("", _result, _state); + _result.Namespaces.ShouldContain("ns1"); + } + + [Theory, ScriptCsAutoData] + public void ShouldSetPackageScriptsInjectedInSession(Mock executor) + { + executor.Protected(); + executor.Setup(e => e.LoadScriptLibraries(It.IsAny())).Returns(_scriptLibrariesPreProcessorResult); + executor.Object.InjectScriptLibraries("", _result, _state); + _state.ContainsKey("ScriptLibrariesInjected"); + } + } - engine.Verify(i => i.Execute(It.IsAny(), It.IsAny(), It.Is(x => !x.PathReferences.Except(defaultReferences).Any()), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + public class TheLoadScriptLibrariesMethod + { + [Theory, ScriptCsAutoData] + public void ShouldPreProcessTheScriptLibrariesFileIfPresent( + [Frozen] Mock fileSystem, + [Frozen] Mock preProcessor, + [Frozen] Mock engine, + [Frozen] TestLogProvider logProvider, + [Frozen] Mock composer) + { + // arrange + fileSystem.Setup(fs => fs.FileExists(It.IsAny())).Returns(true); + var executor = new ScriptExecutor( + fileSystem.Object, preProcessor.Object, engine.Object, logProvider, composer.Object, new ScriptInfo()); + + // act + executor.LoadScriptLibraries(""); + + // assert + preProcessor.Verify(p => p.ProcessFile(It.IsAny())); } } @@ -322,16 +637,19 @@ public class TheAddReferencesMethod [Theory, ScriptCsAutoData] public void ShouldAddReferenceToEachAssembly(ScriptExecutor executor) { - var calling = Assembly.GetCallingAssembly(); - var executing = Assembly.GetExecutingAssembly(); - var entry = Assembly.GetEntryAssembly(); - executor.AddReferences(calling, executing, entry, entry); - - executor.References.Assemblies.ShouldContain(calling); - executor.References.Assemblies.ShouldContain(executing); - executor.References.Assemblies.ShouldContain(entry); - executor.References.Assemblies.Count.ShouldEqual(3); - } + // arrange + var assembly1 = typeof(Mock).Assembly; + var assembly2 = typeof(FrozenAttribute).Assembly; + var assembly3 = typeof(Assert).Assembly; + + // act + executor.AddReferences(assembly1, assembly2, assembly3, assembly3); + + // assert + executor.References.Assemblies.ShouldContain(assembly1); + executor.References.Assemblies.ShouldContain(assembly2); + executor.References.Assemblies.ShouldContain(assembly3); + } } public class TheRemoveReferencesMethod @@ -339,17 +657,20 @@ public class TheRemoveReferencesMethod [Theory, ScriptCsAutoData] public void ShouldRemoveReferenceToEachAssembly(ScriptExecutor executor) { - var calling = Assembly.GetCallingAssembly(); - var executing = Assembly.GetExecutingAssembly(); - var entry = Assembly.GetEntryAssembly(); - executor.AddReferences(calling, executing, entry); - executor.RemoveReferences(calling, executing); - - executor.References.Assemblies.ShouldNotContain(calling); - executor.References.Assemblies.ShouldNotContain(executing); - executor.References.Assemblies.ShouldContain(entry); - executor.References.Assemblies.Count.ShouldEqual(1); + // arrange + var assembly1 = typeof(Mock).Assembly; + var assembly2 = typeof(FrozenAttribute).Assembly; + var assembly3 = typeof(Assert).Assembly; + executor.AddReferences(assembly1, assembly2, assembly3); + + // act + executor.RemoveReferences(assembly1, assembly2); + + // assert + executor.References.Assemblies.ShouldNotContain(assembly1); + executor.References.Assemblies.ShouldNotContain(assembly2); + executor.References.Assemblies.ShouldContain(assembly3); } } } -} \ No newline at end of file +} diff --git a/test/ScriptCs.Core.Tests/ScriptHostTests.cs b/test/ScriptCs.Core.Tests/ScriptHostTests.cs index 7f17cd9b..a90e169a 100644 --- a/test/ScriptCs.Core.Tests/ScriptHostTests.cs +++ b/test/ScriptCs.Core.Tests/ScriptHostTests.cs @@ -12,31 +12,37 @@ public class ScriptHostTests { public class TheGetMethod { - private Mock _mockContext = new Mock(); - private Mock _mockScriptPackManager = new Mock(); - private ScriptHost _scriptHost; + private readonly Mock _mockContext = new Mock(); + private readonly Mock _mockScriptPackManager = new Mock(); + private readonly Mock _mockConsole = new Mock(); + private readonly Mock _mockSerializer = new Mock(); + + private readonly ScriptHost _scriptHost; public TheGetMethod() { - _scriptHost = new ScriptHost(_mockScriptPackManager.Object, new ScriptEnvironment(new string[0])); + _scriptHost = new ScriptHost(_mockScriptPackManager.Object, new ScriptEnvironment(new string[0], _mockConsole.Object, new Printers(_mockSerializer.Object))); _mockScriptPackManager.Setup(s => s.Get()).Returns(_mockContext.Object); } [Fact] public void ShoulGetScriptPackFromScriptPackManagerWhenInvoked() { - var result = _scriptHost.Require(); + _scriptHost.Require(); _mockScriptPackManager.Verify(s => s.Get()); } } public class TheConstructor { + private readonly Mock _mockConsole = new Mock(); + private readonly Mock _mockSerializer = new Mock(); + [Fact] public void ShouldSetScriptEnvironment() { - var environment = new ScriptEnvironment(new string[0]); - var scriptHost = new ScriptHost(null, environment); + var environment = new ScriptEnvironment(new string[0], _mockConsole.Object, new Printers(_mockSerializer.Object)); + var scriptHost = new ScriptHost(new Mock().Object, environment); scriptHost.Env.ShouldEqual(environment); } diff --git a/test/ScriptCs.Core.Tests/ScriptLibraryComposerTests.cs b/test/ScriptCs.Core.Tests/ScriptLibraryComposerTests.cs new file mode 100644 index 00000000..7818f2bb --- /dev/null +++ b/test/ScriptCs.Core.Tests/ScriptLibraryComposerTests.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ScriptCs.Contracts; +using Moq; +using Moq.Protected; +using Should; +using Xunit; +using AutoFixture.Xunit2; + +namespace ScriptCs.Tests +{ + public class ScriptLibraryComposerTests + { + public class TheGetMainScriptMethod + { + [Theory, ScriptCsAutoData] + public void ShouldReturnTheScript(Mock package, ScriptLibraryComposer composer) + { + var files = new[] {"file.csx", "fileMain.csx", "file"}; + package.Setup(p => p.GetContentFiles()).Returns(files); + var script = composer.GetMainScript(package.Object); + script.ShouldEqual("fileMain.csx"); + } + } + + public class TheProcessPackageMethod + { + [Theory, ScriptCsAutoData] + public void ShouldFindThePackage( + [Frozen] Mock packageContainer, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.Setup(p=>p.GetContentFiles()).Returns(Enumerable.Empty()); + composer.ProcessPackage("", reference.Object, new StringBuilder(), new List(), new List()); + packageContainer.Verify(c => c.FindPackage(It.IsAny(), It.IsAny())); + } + + [Theory, ScriptCsAutoData] + public void ShouldLogAWarningIfThePackageIsMissing( + [Frozen] TestLogProvider logProvider, + [Frozen] Mock packageContainer, + ScriptLibraryComposer composer, + Mock reference) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns((IPackageObject)null); + reference.SetupGet(r => r.PackageId).Returns("test"); + composer.ProcessPackage("", reference.Object, new StringBuilder(), new List(), new List()); + packageContainer.Verify(c => c.FindPackage(It.IsAny(), It.IsAny())); + logProvider.Output.ShouldContain("WARN: Package missing: test"); + } + + + + [Theory, ScriptCsAutoData] + public void ShouldPreProcessTheScriptFile( + [Frozen] Mock packageContainer, + [Frozen] Mock preProcessor, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.Setup(p => p.GetContentFiles()).Returns(new List {"TestMain.csx"}); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + composer.ProcessPackage(@"c:\packages", reference.Object, new StringBuilder(), new List(), new List()); + preProcessor.Verify(p=> p.ProcessFile(It.IsAny())); + } + + [Theory, ScriptCsAutoData] + public void ShouldWarnIfMultipleMainFilesArePresent( + [Frozen] Mock packageContainer, + [Frozen] Mock preProcessor, + [Frozen] TestLogProvider logProvider, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.SetupGet(p => p.FullName).Returns("Test"); + package.Setup(p => p.GetContentFiles()).Returns(new List { "Test1Main.csx", "Test2Main.csx" }); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + composer.ProcessPackage(@"c:\packages", reference.Object, new StringBuilder(), new List(), new List()); + logProvider.Output.ShouldContain( + "WARN: Script Libraries in 'Test' ignored due to multiple Main files being present"); + } + + + + [Theory, ScriptCsAutoData] + public void ShouldAppendTheClassWrapper( + [Frozen] Mock packageContainer, + [Frozen] Mock preProcessor, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.Setup(p => p.GetContentFiles()).Returns(new List {"TestMain.csx"}); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult()); + var builder = new StringBuilder(); + composer.ProcessPackage(@"c:\packages", reference.Object, builder, new List(), new List()); + builder.ToString().ShouldContain("public class Test : ScriptCs.ScriptLibraryWrapper {"); + } + + [Theory, ScriptCsAutoData] + public void ShouldAppendThePreProcessedCode( + [Frozen] Mock packageContainer, + [Frozen] Mock preProcessor, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.Setup(p => p.GetContentFiles()).Returns(new List { "TestMain.csx" }); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult {Code="TestCode"}); + var builder = new StringBuilder(); + composer.ProcessPackage(@"c:\packages", reference.Object, builder, new List(), new List()); + builder.ToString().EndsWith("TestCode}"); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddsReferences( + [Frozen] Mock packageContainer, + [Frozen] Mock preProcessor, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.Setup(p => p.GetContentFiles()).Returns(new List { "TestMain.csx" }); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "TestCode", References = new List {"ref1", "ref2"}}); + var refs = new List(); + composer.ProcessPackage(@"c:\packages", reference.Object, new StringBuilder(), refs, new List()); + refs.ShouldContain("ref1"); + refs.ShouldContain("ref2"); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddNamespaces( + [Frozen] Mock packageContainer, + [Frozen] Mock preProcessor, + ScriptLibraryComposer composer, + Mock reference, + Mock package) + { + packageContainer.Setup(c => c.FindPackage(It.IsAny(), It.IsAny())) + .Returns(package.Object); + package.Setup(p => p.GetContentFiles()).Returns(new List { "TestMain.csx" }); + preProcessor.Setup(p => p.ProcessFile(It.IsAny())).Returns(new FilePreProcessorResult { Code = "TestCode", Namespaces = new List { "ns1", "ns2" } }); + var ns = new List(); + composer.ProcessPackage(@"c:\packages", reference.Object, new StringBuilder(), new List(), ns); + ns.ShouldContain("ns1"); + ns.ShouldContain("ns2"); + } + } + + public class TheComposeMethod + { + [Theory, ScriptCsAutoData] + public void ShouldProcessEachPackage( + [Frozen] Mock resolver, + Mock composer, + Mock reference1, + Mock reference2) + { + composer.Protected(); + resolver.Setup(r=>r.GetPackages(It.IsAny())).Returns(new List { reference1.Object, reference2.Object }); + composer.Setup(c=>c.ProcessPackage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())); + composer.Object.Compose("workingdirectory"); + composer.Verify(c=>c.ProcessPackage(It.IsAny(),reference1.Object, It.IsAny(), It.IsAny>(), It.IsAny>())); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddEachNamespace( + [Frozen] Mock resolver, + Mock composer, + Mock reference1 + ) + { + composer.Protected(); + var builder = new StringBuilder(); + composer.Setup( + c => + c.ProcessPackage(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) + .Callback((string p, IPackageReference r, StringBuilder b, List refs, List ns) => + { + ns.Add("ns1"); + ns.Add("ns2"); + }); + resolver.Setup(r=>r.GetPackages(It.IsAny())).Returns(new List { reference1.Object }); + composer.Object.Compose("workingdirectory", builder); + var code = builder.ToString(); + code.ShouldContain(string.Format("using ns1;{0}", Environment.NewLine)); + code.ShouldContain(string.Format("using ns2;{0}", Environment.NewLine)); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddEachReference( + [Frozen] Mock resolver, + Mock composer, + Mock reference1 + ) + { + composer.Protected(); + var builder = new StringBuilder(); + composer.Setup( + c => + c.ProcessPackage(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) + .Callback((string p, IPackageReference r, StringBuilder b, List refs, List ns) => + { + refs.Add("ref1"); + refs.Add("ref2"); + }); + resolver.Setup(r => r.GetPackages(It.IsAny())).Returns(new List { reference1.Object }); + composer.Object.Compose("workingdirectory", builder); + var code = builder.ToString(); + code.ShouldContain(string.Format("#r ref1{0}", Environment.NewLine)); + code.ShouldContain(string.Format("#r ref2{0}", Environment.NewLine)); + } + + [Theory, ScriptCsAutoData] + public void ShouldWriteTheScriptLibrariesFile( + [Frozen] Mock fileSystem, + [Frozen] Mock resolver, + ScriptLibraryComposer composer + ) + { + var builder = new StringBuilder("Test"); + fileSystem.Setup(fs => fs.WriteToFile(It.IsAny(), It.IsAny())); + resolver.Setup(r => r.GetPackages(It.IsAny())).Returns(Enumerable.Empty()); + composer.Compose("workingdirectory", builder); + fileSystem.Verify(fs => fs.WriteToFile(It.IsAny(), It.Is(v=>v.Equals("Test")))); + } + } + } +} diff --git a/test/ScriptCs.Core.Tests/ShebangLineProcessorTests.cs b/test/ScriptCs.Core.Tests/ShebangLineProcessorTests.cs new file mode 100644 index 00000000..7b8ff5a3 --- /dev/null +++ b/test/ScriptCs.Core.Tests/ShebangLineProcessorTests.cs @@ -0,0 +1,39 @@ +using Should; +using Xunit.Extensions; +using ScriptCs.Contracts; +using Xunit; + +namespace ScriptCs.Tests +{ + public class ShebangLineProcessorTests + { + public class TheProcessLineMethod + { + [Theory, ScriptCsAutoData] + public void ShouldReturnTrueOnShebangLine(IFileParser parser, ShebangLineProcessor processor) + { + // Arrange + const string Line = @"#!/usr/bin/env scriptcs"; + + // Act + var result = processor.ProcessLine(parser, new FileParserContext(), Line, true); + + // Assert + result.ShouldBeTrue(); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnFalseOtherwise(IFileParser parser, ShebangLineProcessor processor) + { + // Arrange + const string Line = @"var x = new Test();"; + + // Act + var result = processor.ProcessLine(parser, new FileParserContext(), Line, true); + + // Assert + result.ShouldBeFalse(); + } + } + } +} \ No newline at end of file diff --git a/test/ScriptCs.Core.Tests/UsingLineProcessorTests.cs b/test/ScriptCs.Core.Tests/UsingLineProcessorTests.cs index 02719f0e..59cfc05b 100644 --- a/test/ScriptCs.Core.Tests/UsingLineProcessorTests.cs +++ b/test/ScriptCs.Core.Tests/UsingLineProcessorTests.cs @@ -1,8 +1,6 @@ using ScriptCs.Contracts; - using Should; - -using Xunit.Extensions; +using Xunit; namespace ScriptCs.Tests { @@ -14,7 +12,7 @@ public class TheProcessLineMethod public void ShouldReturnTrueOnUsingLine(IFileParser parser, UsingLineProcessor processor) { // Arrange - const string UsingLine = @"using ""System.Data"";"; + const string UsingLine = "using System.Data;"; // Act var result = processor.ProcessLine(parser, new FileParserContext(), UsingLine, true); @@ -36,11 +34,38 @@ public void ShouldReturnFalseOtherwise(IFileParser parser, UsingLineProcessor pr result.ShouldBeFalse(); } + [Theory, ScriptCsAutoData] + public void ShouldIgnoreAliases(IFileParser parser, UsingLineProcessor processor) + { + // Arrange + const string UsingLine = "using Path = System.IO.Path"; + + // Act + var result = processor.ProcessLine(parser, new FileParserContext(), UsingLine, true); + + // Assert + result.ShouldBeFalse(); + } + + [Theory, ScriptCsAutoData] + public void ShouldIgnoreUsingStatic(IFileParser parser, UsingLineProcessor processor) + { + // Arrange + const string UsingLine = "using static System.Console;"; + var context = new FileParserContext(); + + // Act + var result = processor.ProcessLine(parser, new FileParserContext(), UsingLine, true); + + // Assert + result.ShouldBeFalse(); + } + [Theory, ScriptCsAutoData] public void ShouldAddNamespaceToContext(IFileParser parser, UsingLineProcessor processor) { // Arrange - const string UsingLine = @"using ""System.Data"";"; + const string UsingLine = "using System.Data;"; var context = new FileParserContext(); // Act diff --git a/test/ScriptCs.Core.Tests/app.config b/test/ScriptCs.Core.Tests/app.config index 280e1ca4..31080f45 100644 --- a/test/ScriptCs.Core.Tests/app.config +++ b/test/ScriptCs.Core.Tests/app.config @@ -3,17 +3,13 @@ - - - - - - + + - - + + - \ No newline at end of file + diff --git a/test/ScriptCs.Core.Tests/packages.config b/test/ScriptCs.Core.Tests/packages.config deleted file mode 100644 index b8044850..00000000 --- a/test/ScriptCs.Core.Tests/packages.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ScriptCs.Engine.Roslyn.Tests/CSharpModuleTests.cs b/test/ScriptCs.Engine.Roslyn.Tests/CSharpModuleTests.cs new file mode 100644 index 00000000..d10fed92 --- /dev/null +++ b/test/ScriptCs.Engine.Roslyn.Tests/CSharpModuleTests.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.Engine.Roslyn; +using Should; +using Xunit; + +namespace ScriptCs.Tests +{ + public class CSharpModuleTests + { + public class TheInitializeMethod + { + private readonly Mock _configMock = new Mock(); + private readonly IModuleConfiguration _config; + private readonly RoslynModule _module = new RoslynModule(); + private readonly IDictionary _overrides = new Dictionary(); + + public TheInitializeMethod() + { + _configMock.SetupGet(c => c.Debug).Returns(false); + _configMock.SetupGet(c => c.IsRepl).Returns(false); + _configMock.SetupGet(c => c.Cache).Returns(false); + _configMock.SetupGet(c => c.Overrides).Returns(_overrides); + _config = _configMock.Object; + } + + [Fact] + public void ShouldNotOverrideTheEngineIfOneIsRegistered() + { + var engine = new Mock(); + _overrides[typeof (IScriptEngine)] = engine.Object; + _module.Initialize(_config); + _overrides[typeof(IScriptEngine)].ShouldEqual(engine.Object); + } + + [Fact] + public void ShouldRegisterThePersistantScriptEngineWhenCacheIsEnabled() + { + _configMock.SetupGet(c => c.Cache).Returns(true); + _module.Initialize(_config); + _overrides[typeof(IScriptEngine)].ShouldEqual(typeof(CSharpPersistentEngine)); + } + + [Fact] + public void ShouldRegisterTheScriptEngineWhenCacheIsDisabled() + { + _module.Initialize(_config); + _overrides[typeof(IScriptEngine)].ShouldEqual(typeof(CSharpScriptEngine)); + } + + [Fact] + public void ShouldRegisterTheInMemoryEngineWhenDebugIsEnabled() + { + _configMock.Setup(c => c.Debug).Returns(true); + _module.Initialize(_config); + _overrides[typeof(IScriptEngine)].ShouldEqual(typeof(CSharpScriptInMemoryEngine)); + } + + [Fact] + public void ShouldRegisterTheReplEngineWhenReplIsEnabled() + { + _configMock.Setup(c => c.IsRepl).Returns(true); + _module.Initialize(_config); + _overrides[typeof(IScriptEngine)].ShouldEqual(typeof(CSharpReplEngine)); + } + } + } +} diff --git a/test/ScriptCs.Engine.Roslyn.Tests/CSharpReplEngineTests.cs b/test/ScriptCs.Engine.Roslyn.Tests/CSharpReplEngineTests.cs new file mode 100644 index 00000000..85a5b243 --- /dev/null +++ b/test/ScriptCs.Engine.Roslyn.Tests/CSharpReplEngineTests.cs @@ -0,0 +1,120 @@ +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using ScriptCs.Contracts; +using ScriptCs.Engine.Roslyn; +using Should; +using Xunit; +using AutoFixture.Xunit2; + +namespace ScriptCs.Tests +{ + public class CSharpReplEngineTests + { + public class GetLocalVariablesMethod + { + [Theory, ScriptCsAutoData] + public void ShouldReturnDeclaredVariables([NoAutoProperties]CSharpReplEngine engine, ScriptPackSession scriptPackSession) + { + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + + engine.Execute("int x = 1;", new string[0], new AssemblyReferences(), Enumerable.Empty(), + scriptPackSession); + engine.Execute(@"var y = ""www"";", new string[0], new AssemblyReferences(), Enumerable.Empty(), + scriptPackSession); + + engine.GetLocalVariables(scriptPackSession).ShouldEqual(new Collection { "System.Int32 x", "System.String y" }); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnOnlyLastValueOfVariablesDeclaredManyTimes([NoAutoProperties]CSharpReplEngine engine, ScriptPackSession scriptPackSession) + { + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + + engine.Execute("int x = 1;", new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); + engine.Execute("int x = 2;", new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); + + engine.GetLocalVariables(scriptPackSession).ShouldEqual(new Collection { "System.Int32 x" }); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturn0VariablesAfterReset([NoAutoProperties]CSharpReplEngine engine, ScriptPackSession scriptPackSession) + { + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + + engine.Execute("int x = 1;", new string[0], new AssemblyReferences(), Enumerable.Empty(), + scriptPackSession); + engine.Execute(@"var y = ""www"";", new string[0], new AssemblyReferences(), Enumerable.Empty(), + scriptPackSession); + + scriptPackSession.State[CommonScriptEngine.SessionKey] = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + + engine.GetLocalVariables(scriptPackSession).ShouldBeEmpty(); + } + } + + public class TheExecuteMethod + { + [Theory, ScriptCsAutoData] + public void ShouldSetIsCompleteSubmissionToFalseIfCodeIsMissingCurlyBracket( + [NoAutoProperties] CSharpReplEngine engine, ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "class test {"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System" }); + + // Act + var result = engine.Execute( + Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.IsCompleteSubmission.ShouldBeFalse(); + } + + [Theory, ScriptCsAutoData] + public void ShouldSetIsCompleteSubmissionToFalseIfCodeIsMissingParenthesis( + [NoAutoProperties] CSharpReplEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "System.Diagnostics.Debug.WriteLine(\"a\""; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.IsCompleteSubmission.ShouldBeFalse(); + } + + [Theory, ScriptCsAutoData] + public void ShouldSetIsCompleteSubmissionToFalseIfCodeIsMissingSquareBracket( + [NoAutoProperties] CSharpReplEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var x = new[1] { 1 }; var y = x[0"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.IsCompleteSubmission.ShouldBeFalse(); + } + } + } +} diff --git a/test/ScriptCs.Engine.Roslyn.Tests/CSharpScriptEngineTests.cs b/test/ScriptCs.Engine.Roslyn.Tests/CSharpScriptEngineTests.cs new file mode 100644 index 00000000..30dd958b --- /dev/null +++ b/test/ScriptCs.Engine.Roslyn.Tests/CSharpScriptEngineTests.cs @@ -0,0 +1,369 @@ +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using Moq; +using ScriptCs.Contracts; +using ScriptCs.Engine.Roslyn; +using Should; +using Xunit; +using AutoFixture.Xunit2; + +namespace ScriptCs.Tests +{ + public class CSharpScriptEngineTests + { + public class TheExecuteMethod + { + private IConsole _console = new Mock().Object; + private IObjectSerializer _serializer = new Mock().Object; + private Printers _printers; + + public TheExecuteMethod() + { + _printers = new Printers(_serializer); + } + + [Theory, ScriptCsAutoData] + public void ShouldCreateScriptHostWithContexts( + [Frozen] Mock scriptHostFactory, + [Frozen] Mock scriptPack, + ScriptPackSession scriptPackSession, + [NoAutoProperties] CSharpScriptEngine engine) + { + // Arrange + const string Code = "var a = 0;"; + + scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) + .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q, _console, _printers))); + + scriptPack.Setup(p => p.Initialize(It.IsAny())); + scriptPack.Setup(p => p.GetContext()).Returns((IScriptPackContext)null); + + // Act + engine.Execute(Code, new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); + + // Assert + scriptHostFactory.Verify(f => f.CreateScriptHost(It.IsAny(), It.IsAny())); + } + + [Theory, ScriptCsAutoData] + public void ShouldReuseExistingSessionIfProvided( + [Frozen] Mock scriptHostFactory, + [NoAutoProperties] CSharpTestScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var a = 0;"; + + scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) + .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q, _console, _printers))); + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + + // Act + engine.Execute(Code, new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); + + // Assert + engine.Session.ShouldEqual(session.Session); + } + + [Fact] + public void ShouldCreateNewSessionIfNotProvided() + { + var scriptHostFactory = new Mock(); + scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) + .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q, _console, _printers))); + + // Arrange + var engine = new CSharpTestScriptEngine(scriptHostFactory.Object, new TestLogProvider()); + const string Code = "var a = 0;"; + + // Act + engine.Execute(Code, new string[0], new AssemblyReferences(), Enumerable.Empty(), new ScriptPackSession(new IScriptPack[0], new string[0])); + + // Assert + engine.Session.ShouldNotBeNull(); + } + + [Theory, ScriptCsAutoData] + public void ShouldAddNewReferencesIfTheyAreProvided( + [NoAutoProperties] CSharpTestScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var a = 0;"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + ((SessionState)scriptPackSession.State[CommonScriptEngine.SessionKey]).References.Paths.Count().ShouldEqual(2); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnAScriptResult( + [NoAutoProperties] CSharpTestScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + var code = string.Empty; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.ShouldBeType(); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnCompileExceptionIfCodeDoesNotCompile( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "this shold not compile"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.CompileExceptionInfo.ShouldNotBeNull(); + } + + [Theory(Skip = "this feature is not supported in Roslyn 1.0.0-rc2: see https://github.com/dotnet/roslyn/issues/1012"), ScriptCsAutoData] + public void ShouldReturnInvalidNamespacesIfCS0241Encountered( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + + // Act + var result = engine.Execute(string.Empty, new string[0], new AssemblyReferences(), new[] { "foo" }, scriptPackSession); + + // Assert + result.CompileExceptionInfo.ShouldNotBeNull(); + result.InvalidNamespaces.ShouldContain("foo"); + } + + //todo: filip: this test will not even compile as it used to do reflection old roslyn assemblies! + //[Theory, ScriptCsAutoData] + //public void ShouldRemoveInvalidNamespacesFromSessionStateAndEngine( + // [Frozen] Mock scriptHostFactory, + // [NoAutoProperties] CSharpScriptEngine engine, + // ScriptPackSession scriptPackSession) + //{ + // var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + // scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + + // // Act + // engine.Execute(string.Empty, new string[0], new AssemblyReferences(), new[] { "System", "foo" }, scriptPackSession); + + // // Assert + // session.Namespaces.ShouldNotBeEmpty(); + // session.Namespaces.ShouldNotContain("foo"); + // var pendingNamespacesField = session.Session.GetType().GetField("pendingNamespaces", BindingFlags.Instance | BindingFlags.NonPublic); + // var pendingNamespacesValue = (ReadOnlyArray)pendingNamespacesField.GetValue(session.Session); + // pendingNamespacesValue.AsEnumerable().ShouldNotBeEmpty(); + // pendingNamespacesValue.AsEnumerable().ShouldNotContain("foo"); + //} + + [Theory, ScriptCsAutoData] + public void ShouldNotReturnCompileExceptionIfCodeDoesCompile( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var theNumber = 42; //this should compile"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.CompileExceptionInfo.ShouldBeNull(); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnExecuteExceptionIfCodeExecutionThrowsException( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "throw new System.Exception(); //this should throw an Exception"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.ExecuteExceptionInfo.ShouldNotBeNull(); + } + + [Theory, ScriptCsAutoData] + public void ShouldNotReturnExecuteExceptionIfCodeExecutionDoesNotThrowAnException( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var theNumber = 42; //this should not throw an Exception"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.ExecuteExceptionInfo.ShouldBeNull(); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnReturnValueIfCodeExecutionReturnsValue( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + const string Code = "\"Hello\" //this should return \"Hello\""; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.ReturnValue.ShouldEqual("Hello"); + } + + [Theory, ScriptCsAutoData] + public void ShouldNotReturnReturnValueIfCodeExecutionDoesNotReturnValue( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var theNumber = 42; //this should not return a value"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.ReturnValue.ShouldBeNull(); + } + + [Theory, ScriptCsAutoData] + public void ShouldNotMarkSubmissionsAsIncompleteWhenRunningScript( + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "class test {"; + + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + engine.FileName = "test.csx"; + + // Act + var result = engine.Execute( + Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.IsCompleteSubmission.ShouldBeTrue(); + result.CompileExceptionInfo.ShouldNotBeNull(); + } + + [Theory, ScriptCsAutoData] + public void ShouldCompileWhenUsingClassesFromAPassedAssemblyInstance( + [Frozen] Mock scriptHostFactory, + [Frozen] ScriptPackSession scriptPackSession) + { + // Arrange + const string Code = "var x = new ScriptCs.Tests.TestMarkerClass();"; + + scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) + .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q, _console, _printers))); + + var engine = new CSharpScriptEngine(scriptHostFactory.Object, new TestLogProvider()); + var session = new SessionState { Session = CSharpScript.RunAsync("").GetAwaiter().GetResult() }; + scriptPackSession.State[CommonScriptEngine.SessionKey] = session; + var refs = new AssemblyReferences(new[] { Assembly.GetExecutingAssembly() }, new[] { "System", "System.Runtime" }); + + // Act + var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + result.CompileExceptionInfo.ShouldBeNull(); + result.ExecuteExceptionInfo.ShouldBeNull(); + } + + + [Theory, ScriptCsAutoData] + public void ShouldInitializeScriptLibraryWrapperHost( + [Frozen] Mock scriptHostFactory, + Mock manager, + [NoAutoProperties] CSharpScriptEngine engine, + ScriptPackSession scriptPackSession + ) + { + // Arrange + const string Code = "var theNumber = 42; //this should compile"; + + var refs = new AssemblyReferences(new[] { "System", "System.Runtime" }); + + scriptHostFactory.Setup(s => s.CreateScriptHost(It.IsAny(), It.IsAny())) + .Returns(new ScriptHost(manager.Object, null)); + + // Act + engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); + + // Assert + ScriptLibraryWrapper.ScriptHost.ShouldNotEqual(null); + } + } + + public class CSharpTestScriptEngine : CSharpScriptEngine + { + public CSharpTestScriptEngine(IScriptHostFactory scriptHostFactory, TestLogProvider logProvider) + : base(scriptHostFactory, logProvider) + { + } + + public ScriptState Session { get; private set; } + + protected override ScriptResult Execute(string code, object globals, SessionState sessionState) + { + base.Execute(code, globals, sessionState); + Session = sessionState.Session; + return ScriptResult.Empty; + } + } + } + + public class TestMarkerClass { } +} diff --git a/test/ScriptCs.Engine.Roslyn.Tests/RoslynScriptInMemoryEngineTests.cs b/test/ScriptCs.Engine.Roslyn.Tests/CSharpScriptInMemoryEngineTests.cs similarity index 66% rename from test/ScriptCs.Engine.Roslyn.Tests/RoslynScriptInMemoryEngineTests.cs rename to test/ScriptCs.Engine.Roslyn.Tests/CSharpScriptInMemoryEngineTests.cs index 8d00b275..1de12db6 100644 --- a/test/ScriptCs.Engine.Roslyn.Tests/RoslynScriptInMemoryEngineTests.cs +++ b/test/ScriptCs.Engine.Roslyn.Tests/CSharpScriptInMemoryEngineTests.cs @@ -1,29 +1,32 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using Common.Logging; using Moq; -using Ploeh.AutoFixture.Xunit; -using Roslyn.Compilers; using ScriptCs.Contracts; using ScriptCs.Engine.Roslyn; using ScriptCs.Exceptions; +using Should; using Xunit; -using Xunit.Extensions; namespace ScriptCs.Tests { - using Should; - - public class RoslynScriptInMemoryEngineTests + public class CSharpScriptInMemoryEngineTests { public class TheExecuteMethod { - [Theory, ScriptCsAutoData] + private IConsole _console = new Mock().Object; + private IObjectSerializer _serializer = new Mock().Object; + private Printers _printers; + + public TheExecuteMethod() + { + _printers = new Printers(_serializer); + } + + [Fact] public void ShouldExposeExceptionThrownByScriptWhenErrorOccurs() { - var scriptEngine = new RoslynScriptInMemoryEngine(new ScriptHostFactory(), new Mock().Object); + var scriptEngine = new CSharpScriptInMemoryEngine(new ScriptHostFactory(_console, _printers, new ScriptInfo()), new TestLogProvider()); // Arrange var lines = new List { @@ -40,19 +43,19 @@ public void ShouldExposeExceptionThrownByScriptWhenErrorOccurs() // Assert var exception = Assert.Throws(() => result.ExecuteExceptionInfo.Throw()); - exception.StackTrace.ShouldContain("at Submission#0"); + exception.StackTrace.ShouldContain("Submission#0"); exception.Message.ShouldContain("InvalidOperationExceptionMessage"); } - [Theory, ScriptCsAutoData] + [Fact] public void ShouldExposeExceptionThrownByCompilation() { - var scriptEngine = new RoslynScriptInMemoryEngine(new ScriptHostFactory(), new Mock().Object); + var scriptEngine = new CSharpScriptInMemoryEngine(new ScriptHostFactory(_console, _printers, new ScriptInfo()), new TestLogProvider()); // Arrange var lines = new List { - "using Sysasdasdasdtem;" + "Sysasdasdasdtem;" }; var code = string.Join(Environment.NewLine, lines); @@ -64,8 +67,7 @@ public void ShouldExposeExceptionThrownByCompilation() // Assert var exception = Assert.Throws(() => result.CompileExceptionInfo.Throw()); - exception.InnerException.ShouldBeType(); - exception.Message.ShouldContain("The type or namespace name 'Sysasdasdasdtem' could not be found"); + exception.Message.ShouldContain("error CS0103: The name 'Sysasdasdasdtem' does not exist in the current context"); } } } diff --git a/test/ScriptCs.Engine.Roslyn.Tests/Properties/AssemblyInfo.cs b/test/ScriptCs.Engine.Roslyn.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 5852ca04..00000000 --- a/test/ScriptCs.Engine.Roslyn.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("ScriptCs.Engine.Roslyn")] -[assembly: AssemblyDescription("")] diff --git a/test/ScriptCs.Engine.Roslyn.Tests/RoslynScriptEngineTests.cs b/test/ScriptCs.Engine.Roslyn.Tests/RoslynScriptEngineTests.cs deleted file mode 100644 index 73696382..00000000 --- a/test/ScriptCs.Engine.Roslyn.Tests/RoslynScriptEngineTests.cs +++ /dev/null @@ -1,412 +0,0 @@ -using System.Linq; -using System.Reflection; -using Common.Logging; -using Moq; - -using Ploeh.AutoFixture.Xunit; -using Roslyn.Compilers; -using Roslyn.Scripting; -using Roslyn.Scripting.CSharp; -using ScriptCs.Contracts; -using ScriptCs.Engine.Roslyn; -using Should; -using Xunit.Extensions; - -namespace ScriptCs.Tests -{ - public class RoslynScriptEngineTests - { - public class Constructor - { - [Theory, ScriptCsAutoData] - public void ShouldAddReferenceToCore() - { - var engine = new RoslynTestScriptEngine(new Mock().Object, new Mock().Object); - engine.Engine.GetReferences().Where(x => x.Display.EndsWith("ScriptCs.Core.dll")).Count().ShouldEqual(1); - } - } - - public class TheExecuteMethod - { - [Theory, ScriptCsAutoData] - public void ShouldCreateScriptHostWithContexts( - [Frozen] Mock scriptHostFactory, - [Frozen] Mock scriptPack, - ScriptPackSession scriptPackSession, - [NoAutoProperties] RoslynScriptEngine engine) - { - // Arrange - const string Code = "var a = 0;"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - scriptPack.Setup(p => p.Initialize(It.IsAny())); - scriptPack.Setup(p => p.GetContext()).Returns((IScriptPackContext)null); - - // Act - engine.Execute(Code, new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); - - // Assert - scriptHostFactory.Verify(f => f.CreateScriptHost(It.IsAny(), It.IsAny())); - } - - [Theory, ScriptCsAutoData] - public void ShouldReuseExistingSessionIfProvided( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynTestScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var a = 0;"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - - // Act - engine.Execute(Code, new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); - - // Assert - engine.Session.ShouldEqual(session.Session); - } - - [Theory, ScriptCsAutoData] - public void ShouldCreateNewSessionIfNotProvided( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynTestScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var a = 0;"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - // Act - engine.Execute(Code, new string[0], new AssemblyReferences(), Enumerable.Empty(), scriptPackSession); - - // Assert - engine.Session.ShouldNotBeNull(); - } - - [Theory, ScriptCsAutoData] - public void ShouldAddNewReferencesIfTheyAreProvided( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynTestScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var a = 0;"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - ((SessionState)scriptPackSession.State[RoslynScriptEngine.SessionKey]).References.PathReferences.Count().ShouldEqual(1); - } - - [Theory, ScriptCsAutoData] - public void ShouldReturnAScriptResult( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynTestScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - var code = string.Empty; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.ShouldBeType(); - } - - [Theory, ScriptCsAutoData] - public void ShouldReturnCompileExceptionIfCodeDoesNotCompile( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "this shold not compile"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.CompileExceptionInfo.ShouldNotBeNull(); - } - - [Theory, ScriptCsAutoData] - public void ShouldNotReturnCompileExceptionIfCodeDoesCompile( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var theNumber = 42; //this should compile"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.CompileExceptionInfo.ShouldBeNull(); - } - - [Theory, ScriptCsAutoData] - public void ShouldReturnExecuteExceptionIfCodeExecutionThrowsException( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "throw new System.Exception(); //this should throw an Exception"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.ExecuteExceptionInfo.ShouldNotBeNull(); - } - - [Theory, ScriptCsAutoData] - public void ShouldNotReturnExecuteExceptionIfCodeExecutionDoesNotThrowAnException( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var theNumber = 42; //this should not throw an Exception"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.ExecuteExceptionInfo.ShouldBeNull(); - } - - [Theory, ScriptCsAutoData] - public void ShouldReturnReturnValueIfCodeExecutionReturnsValue( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - const string Code = "\"Hello\" //this should return \"Hello\""; - - // Arrange - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.ReturnValue.ShouldEqual("Hello"); - } - - [Theory, ScriptCsAutoData] - public void ShouldNotReturnReturnValueIfCodeExecutionDoesNotReturnValue( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var theNumber = 42; //this should not return a value"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.ReturnValue.ShouldBeNull(); - } - - [Theory, ScriptCsAutoData] - public void ShouldSetIsPendingClosingCharToTrueIfCodeIsMissingCurlyBracket( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "class test {"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute( - Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.IsPendingClosingChar.ShouldBeTrue(); - result.ExpectingClosingChar.ShouldEqual('}'); - } - - [Theory, ScriptCsAutoData] - public void ShouldSetIsPendingClosingCharToTrueIfCodeIsMissingSquareBracket( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var x = new[1] { 1 }; var y = x[0"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.IsPendingClosingChar.ShouldBeTrue(); - result.ExpectingClosingChar.ShouldEqual(']'); - } - - [Theory, ScriptCsAutoData] - public void ShouldSetIsPendingClosingCharToTrueIfCodeIsMissingParenthesis( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "System.Diagnostics.Debug.WriteLine(\"a\""; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, new ScriptEnvironment(q))); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.IsPendingClosingChar.ShouldBeTrue(); - result.ExpectingClosingChar.ShouldEqual(')'); - } - } - - [Theory, ScriptCsAutoData] - public void ShouldCompileWhenUsingClassesFromAPassedAssemblyInstance( - [Frozen] Mock scriptHostFactory, - [NoAutoProperties] RoslynScriptEngine engine, - ScriptPackSession scriptPackSession) - { - // Arrange - const string Code = "var x = new ScriptCs.Tests.TestMarkerClass();"; - - scriptHostFactory.Setup(f => f.CreateScriptHost(It.IsAny(), It.IsAny())) - .Returns((p, q) => new ScriptHost(p, q)); - - var session = new SessionState { Session = new ScriptEngine().CreateSession() }; - scriptPackSession.State[RoslynScriptEngine.SessionKey] = session; - var refs = new AssemblyReferences(); - refs.PathReferences.Add("System"); - refs.Assemblies.Add(Assembly.GetExecutingAssembly()); - - // Act - var result = engine.Execute(Code, new string[0], refs, Enumerable.Empty(), scriptPackSession); - - // Assert - result.CompileExceptionInfo.ShouldBeNull(); - result.ExecuteExceptionInfo.ShouldBeNull(); - } - - public class RoslynTestScriptEngine : RoslynScriptEngine - { - public RoslynTestScriptEngine(IScriptHostFactory scriptHostFactory, ILog logger) - : base(scriptHostFactory, logger) - { - } - - public Session Session { get; private set; } - - protected override ScriptResult Execute(string code, Session session) - { - Session = session; - return new ScriptResult(); - } - - internal ScriptEngine Engine { - get { return ScriptEngine; } - } - } - } - - public class TestMarkerClass { } -} \ No newline at end of file diff --git a/test/ScriptCs.Engine.Roslyn.Tests/ScriptCs.Engine.Roslyn.Tests.csproj b/test/ScriptCs.Engine.Roslyn.Tests/ScriptCs.Engine.Roslyn.Tests.csproj index a371a7f9..bb8f8ea1 100644 --- a/test/ScriptCs.Engine.Roslyn.Tests/ScriptCs.Engine.Roslyn.Tests.csproj +++ b/test/ScriptCs.Engine.Roslyn.Tests/ScriptCs.Engine.Roslyn.Tests.csproj @@ -1,86 +1,29 @@ - - - + - {28D11DE5-9F98-4E0A-8CCC-9CDC19110451} - Library - ScriptCs.Tests - ScriptCs.Engine.Roslyn.Tests - ..\..\ - ..\..\ScriptCs.Test.ruleset + net461 + win - - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - ..\..\packages\AutoFixture.3.6.5\lib\net40\Ploeh.AutoFixture.dll - - - ..\..\packages\AutoFixture.AutoMoq.3.6.5\lib\net40\Ploeh.AutoFixture.AutoMoq.dll - - - ..\..\packages\AutoFixture.Xunit.3.6.5\lib\net40\Ploeh.AutoFixture.Xunit.dll - - - ..\..\packages\Roslyn.Compilers.Common.1.2.20906.2\lib\net45\Roslyn.Compilers.dll - - - ..\..\packages\Roslyn.Compilers.CSharp.1.2.20906.2\lib\net45\Roslyn.Compilers.CSharp.dll - - - False - ..\..\packages\Should.1.1.20\lib\Should.dll - - - - - - - - - - ..\..\packages\xunit.1.9.1\lib\net20\xunit.dll - - - ..\..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll - + + + - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - ScriptCsAutoDataAttribute.cs - - - - + + + + + + + + + - - + + - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - {e590e710-e159-48e6-a3e6-1a83d3fe732c} - ScriptCs.Core - - - {e79ec231-e27d-4057-91c9-2d001a3a8c3b} - ScriptCs.Engine.Roslyn - + - - \ No newline at end of file diff --git a/test/ScriptCs.Engine.Roslyn.Tests/app.config b/test/ScriptCs.Engine.Roslyn.Tests/app.config index 280e1ca4..2aaf612c 100644 --- a/test/ScriptCs.Engine.Roslyn.Tests/app.config +++ b/test/ScriptCs.Engine.Roslyn.Tests/app.config @@ -3,17 +3,17 @@ - - + + - - + + - - + + - \ No newline at end of file + diff --git a/test/ScriptCs.Engine.Roslyn.Tests/packages.config b/test/ScriptCs.Engine.Roslyn.Tests/packages.config deleted file mode 100644 index bd2d8a18..00000000 --- a/test/ScriptCs.Engine.Roslyn.Tests/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ScriptCs.Hosting.Tests/ModuleConfigurationTests.cs b/test/ScriptCs.Hosting.Tests/ModuleConfigurationTests.cs new file mode 100644 index 00000000..68b8ccb2 --- /dev/null +++ b/test/ScriptCs.Hosting.Tests/ModuleConfigurationTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using ScriptCs.Contracts; +using Should; +using Xunit; + +namespace ScriptCs.Hosting.Tests +{ + public class ModuleConfigurationTests + { + public class TheLineProcessorMethod + { + [Fact] + public void ShouldAddTheLineProcessorTypeToTheOverridesDictionary() + { + var overrides = new Dictionary(); + + var moduleConfiguration = new ModuleConfiguration(false, "script1.csx", false, LogLevel.Debug, false, overrides); + moduleConfiguration.LineProcessor(); + + var processors = overrides[typeof(ILineProcessor)] as List; + processors.ShouldContain(typeof(UsingLineProcessor)); + } + + [Fact] + public void ShouldReturnTheModuleConfiguration() + { + var moduleConfiguration = new ModuleConfiguration(false, "script1.csx", false, LogLevel.Debug, false, new Dictionary()); + var config = moduleConfiguration.LineProcessor(); + config.ShouldImplement(); + config.ShouldEqual(moduleConfiguration); + } + } + } +} diff --git a/test/ScriptCs.Hosting.Tests/ModuleLoaderTests.cs b/test/ScriptCs.Hosting.Tests/ModuleLoaderTests.cs index 92f45502..505b8d3d 100644 --- a/test/ScriptCs.Hosting.Tests/ModuleLoaderTests.cs +++ b/test/ScriptCs.Hosting.Tests/ModuleLoaderTests.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition.Hosting; +using System.IO; using System.Linq; using System.Reflection; -using Common.Logging; using Moq; using ScriptCs.Contracts; using Should; @@ -11,23 +11,27 @@ namespace ScriptCs.Hosting.Tests { + using ScriptCs.Tests; + public class ModuleLoaderTests { public class TheLoadMethod { private Mock _mockAssemblyResolver = new Mock(); - private IList _paths = new List(); private IList> _modules = new List>(); private Func>> _getModules; - private Mock _mockLogger = new Mock(); + private TestLogProvider _logProvider = new TestLogProvider(); private Mock _mockModule1 = new Mock(); private Mock _mockModule2 = new Mock(); private Mock _mockModule3 = new Mock(); + private Mock _mockModule4 = new Mock(); + private Mock _mockFileSystem = new Mock(); + private Mock _mockAssemblyUtility = new Mock(); public TheLoadMethod() { - var paths = new[] { "path1", "path2" }; - _mockAssemblyResolver.Setup(r => r.GetAssemblyPaths(It.IsAny())).Returns(paths); + var paths = new[] { "path1.dll", "path2.dll" }; + _mockAssemblyResolver.Setup(r => r.GetAssemblyPaths(It.IsAny(), true)).Returns(paths); _modules.Add( new Lazy( () => _mockModule1.Object, new ModuleMetadata { Extensions = "ext1,ext2", Name = "module1" })); @@ -35,50 +39,128 @@ public TheLoadMethod() new Lazy( () => _mockModule2.Object, new ModuleMetadata { Extensions = "ext3,ext4", Name = "module2" })); _modules.Add(new Lazy(() => _mockModule3.Object, new ModuleMetadata { Name = "module3" })); + _modules.Add(new Lazy(() => _mockModule4.Object, new ModuleMetadata { Name = "module4", Autoload = true })); _getModules = c => _modules; + _mockFileSystem.Setup(f=>f.EnumerateFiles(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Enumerable.Empty()); + _mockAssemblyUtility.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); } [Fact] public void ShouldResolvePathsFromTheAssemblyResolver() { - var loader = new ModuleLoader(_mockAssemblyResolver.Object, _mockLogger.Object, (p, c) => { }, c => Enumerable.Empty>()); - loader.Load(null, "c:\test", null); - _mockAssemblyResolver.Verify(r => r.GetAssemblyPaths("c:\test")); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (p, c) => { }, c => Enumerable.Empty>(), _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new[] { "c:\test" }, null, null); + _mockAssemblyResolver.Verify(r => r.GetAssemblyPaths("c:\test", true)); } [Fact] public void ShouldInvokeTheCatalogActionForEachFile() { - var loader = new ModuleLoader(_mockAssemblyResolver.Object, _mockLogger.Object, (p, c) => _paths.Add(p), c => Enumerable.Empty>()); - loader.Load(null, "c:\test", null); - _paths.Count.ShouldEqual(2); + var assemblies = new List(); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => assemblies.Add(a), c => Enumerable.Empty>(), _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new[] { "c:\test" }, null, null); + assemblies.Count.ShouldEqual(2); + } + + [Fact] + public void ShouldIgnoreLoadingNativeAssemblies() + { + _mockAssemblyUtility.Setup(a => a.IsManagedAssembly(It.Is(f => f == "managed.dll"))).Returns(true); + _mockAssemblyUtility.Setup(a => a.IsManagedAssembly(It.Is(f => f == "native.dll"))).Returns(false); + var mockAssemblies = new List {"managed.dll", "native.dll"}; + _mockAssemblyResolver.Setup(a => a.GetAssemblyPaths(It.IsAny(), true)).Returns(mockAssemblies); + var assemblies = new List(); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => assemblies.Add(a), c => Enumerable.Empty>(), _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new[] { "c:\test" }, null, null); + assemblies.Count.ShouldEqual(1); } [Fact] public void ShouldInitializeModulesThatMatchOnExtension() { - var loader = new ModuleLoader(_mockAssemblyResolver.Object, _mockLogger.Object, (p, c) => _paths.Add(p), _getModules); - loader.Load(null, null, "ext1"); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new string[0], null, "ext1"); _mockModule1.Verify(m => m.Initialize(It.IsAny()), Times.Once()); _mockModule2.Verify(m => m.Initialize(It.IsAny()), Times.Never()); _mockModule3.Verify(m => m.Initialize(It.IsAny()), Times.Never()); } [Fact] - public void ShouldInitializeModulesTheMatchBasedOnName() + public void ShouldInitializeModulesThatMatchBasedOnName() { - var loader = new ModuleLoader(_mockAssemblyResolver.Object, _mockLogger.Object, (p, c) => _paths.Add(p), _getModules); - loader.Load(null, null, null, "module3"); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new string[0], null, null, "module3"); _mockModule1.Verify(m => m.Initialize(It.IsAny()), Times.Never()); _mockModule2.Verify(m => m.Initialize(It.IsAny()), Times.Never()); _mockModule3.Verify(m => m.Initialize(It.IsAny()), Times.Once()); } + [Fact] + public void ShouldInitializeModulesThatAreSetToAutoload() + { + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new string[0], null, null); + _mockModule4.Verify(m => m.Initialize(It.IsAny()), Times.Once()); + } + + [Fact] + public void ShouldNotInitializeModulesThatAreNotSetToAutoload() + { + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new string[0], null, null); + _mockModule1.Verify(m => m.Initialize(It.IsAny()), Times.Never()); + } + + [Fact] + public void ShouldLoadEngineAssemblyByHandIfItsTheOnlyModule() + { + var path = Path.Combine("c:\\foo", ModuleLoader.DefaultCSharpModules["roslyn"]); + _mockAssemblyUtility.Setup(x => x.LoadFile(path)); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + loader.Load(null, new string[0], "c:\\foo", ModuleLoader.DefaultCSharpExtension, "roslyn"); + + _mockAssemblyUtility.Verify(x => x.LoadFile(path), Times.Once()); + } + + [Fact] + public void ShouldLoadEngineModuleFromFile() + { + _mockAssemblyUtility.Setup(x => x.LoadFile(It.IsAny())).Returns(typeof (DummyModule).Assembly); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + + var config = new ModuleConfiguration(true, string.Empty, false, LogLevel.Debug, true, + new Dictionary {{typeof (string), "not loaded"}}); + loader.Load(config, new string[0], "c:\\foo", ModuleLoader.DefaultCSharpExtension, "roslyn"); + + config.Overrides[typeof(string)].ShouldEqual("module loaded"); + } + + [Fact] + public void ShouldNotLoadEngineAssemblyByHandIfItsTheOnlyModuleButExtensionIsNotDefault() + { + var path = Path.Combine("c:\\foo", ModuleLoader.DefaultCSharpModules["roslyn"]); + _mockAssemblyUtility.Setup(x => x.LoadFile(path)); + var loader = new ModuleLoader(_mockAssemblyResolver.Object, _logProvider, (a, c) => { }, _getModules, _mockFileSystem.Object, _mockAssemblyUtility.Object); + + loader.Load(null, new string[0], "c:\\foo", ".fsx", "roslyn"); + _mockAssemblyUtility.Verify(x => x.LoadFile(It.IsAny()), Times.Never); + } + public class ModuleMetadata : IModuleMetadata { public string Name { get; set; } public string Extensions { get; set; } + + public bool Autoload { get; set; } + } + + public class DummyModule : IModule + { + public void Initialize(IModuleConfiguration config) + { + config.Overrides[typeof (string)] = "module loaded"; + } } } } diff --git a/test/ScriptCs.Hosting.Tests/ObjectSerializerTests.cs b/test/ScriptCs.Hosting.Tests/ObjectSerializerTests.cs index c44ad8ea..0be8e25f 100644 --- a/test/ScriptCs.Hosting.Tests/ObjectSerializerTests.cs +++ b/test/ScriptCs.Hosting.Tests/ObjectSerializerTests.cs @@ -1,6 +1,7 @@ using System; - +using Should; using Xunit; +using System.Diagnostics; namespace ScriptCs.Hosting.Tests { @@ -15,38 +16,117 @@ public TheSerializeMethod() _serializer = new ObjectSerializer(); } + [Fact] + public void ShouldSerialize() + { + // arrange + var obj = new Foo + { + Bar = new Bar + { + Baz = true, + Bazz = 123.4, + Bazzz = "hello", + }, + }; + + // act + var result = _serializer.Serialize(obj); + + // assert + result.ShouldEqual( +@"{ + ""Bar"": { + ""Baz"": true, + ""Bazz"": 123.4, + ""Bazzz"": ""hello"" + } +}"); + } + [Fact] public void ShouldSerializeTypeMethods() { - Assert.DoesNotThrow(() => _serializer.Serialize(typeof(Type).GetMethods())); + // arrange + var obj = typeof(Type).GetMethods(); + + // act + var exception = Record.Exception(() => _serializer.Serialize(obj)); + + // assert + exception.ShouldBeNull(); } [Fact] public void ShouldSerializeDelegates() { - Assert.DoesNotThrow(() => _serializer.Serialize(new Action(() => { }))); - Assert.DoesNotThrow(() => _serializer.Serialize(new Foo + // arrange + var obj = new FuncAndAction { Action = () => { }, - Func = () => "Hello World" - })); + Func = () => "Hello World", + }; + + // act + var exception = Record.Exception(() => _serializer.Serialize(obj)); + + // assert + exception.ShouldBeNull(); + } + + [Fact] + public void ShouldNotShowExceptionForUnserializableTypes() + { + var obj = new Process(); + + string result = null; + var exception = Record.Exception(() => result = _serializer.Serialize(obj)); + exception.ShouldBeNull(); + Assert.Equal("Couldn't serialize a returned instance of System.Diagnostics.Process", result); } [Fact] public void ShouldSerializeWithCircularReferences() { - var foo = new Foo(); - foo.Parent = foo; + // arrange + var obj = new Circular(); + obj.Parent = obj; + + // act + var result = _serializer.Serialize(obj); - Assert.DoesNotThrow(() => _serializer.Serialize(foo)); + // assert + result.ShouldEqual( +@"{ + ""$id"": ""1"", + ""Parent"": { + ""$ref"": ""1"" + } +}"); } private class Foo + { + public Bar Bar { get; set; } + } + + private class Bar + { + public bool Baz { get; set; } + public double Bazz { get; set; } + public string Bazzz { get; set; } + } + + private class FuncAndAction { public Action Action { get; set; } public Func Func { get; set; } - public Foo Parent { get; set; } + } + + private class Circular + { + public Circular Parent { get; set; } } } } -} \ No newline at end of file +} diff --git a/test/ScriptCs.Hosting.Tests/PackageInstallerTests.cs b/test/ScriptCs.Hosting.Tests/PackageInstallerTests.cs index f3ee8140..c0947536 100644 --- a/test/ScriptCs.Hosting.Tests/PackageInstallerTests.cs +++ b/test/ScriptCs.Hosting.Tests/PackageInstallerTests.cs @@ -1,16 +1,14 @@ using System; using System.Collections.Generic; -using System.Linq; -using Common.Logging; using Moq; using NuGet; using ScriptCs.Contracts; using ScriptCs.Hosting.Package; +using ScriptCs.Tests; using Should; using Xunit; -using PackageReference = ScriptCs.Package.PackageReference; -namespace ScriptCs.Tests +namespace ScriptCs.Hosting.Tests { public class PackageInstallerTests { @@ -19,18 +17,14 @@ public class InstallPackagesMethod [Fact] public void ShouldThrowArgumentNullExWhenNoPackageIdsPassed() { - var installer = new PackageInstaller(new Mock().Object, new Mock().Object); + var installer = new PackageInstaller(new Mock().Object, new TestLogProvider()); Assert.Throws(() => installer.InstallPackages(null)); } [Fact] public void ShouldInstallAllPassedPackages() { - var logger = new Mock(); var provider = new Mock(); - provider.Setup( - i => i.InstallPackage(It.IsAny(), It.IsAny())) - .Returns(true); var references = new List { @@ -39,7 +33,7 @@ public void ShouldInstallAllPassedPackages() new PackageReference("testId3", VersionUtility.ParseFrameworkName("net40"), new Version("5.0")) }; - var installer = new PackageInstaller(provider.Object, logger.Object); + var installer = new PackageInstaller(provider.Object, new TestLogProvider()); installer.InstallPackages(references); provider.Verify(i => i.InstallPackage(It.IsAny(), It.IsAny()), Times.Exactly(3)); @@ -48,14 +42,10 @@ public void ShouldInstallAllPassedPackages() [Fact] public void ShouldShowErrorIfOneOfPackagesFail() { - var logger = new Mock(); var provider = new Mock(); - provider.Setup( - i => i.InstallPackage(It.IsAny(), It.IsAny())) - .Returns(true); provider.Setup( i => i.InstallPackage(It.Is(x => x.PackageId == "testId"), It.IsAny())) - .Returns(false); + .Throws(); var references = new List { @@ -64,39 +54,17 @@ public void ShouldShowErrorIfOneOfPackagesFail() new PackageReference("testId3", VersionUtility.ParseFrameworkName("net40"), new Version("5.0")) }; - var installer = new PackageInstaller(provider.Object, logger.Object); - installer.InstallPackages(references, true); + var installer = new PackageInstaller(provider.Object, new TestLogProvider()); + var exception = Record.Exception(() => installer.InstallPackages(references, true)); provider.Verify(i => i.InstallPackage(It.IsAny(), It.IsAny()), Times.Exactly(3)); - logger.Verify(i => i.Info(It.Is(x => x.EndsWith("unsuccessful."))), Times.Once()); - } - - [Fact] - public void ShouldShowSuccessIfNoneOfPackagesFail() - { - var logger = new Mock(); - var provider = new Mock(); - provider.Setup( - i => i.InstallPackage(It.IsAny(), It.IsAny())) - .Returns(true); - - var references = new List - { - new PackageReference("testId", VersionUtility.ParseFrameworkName("net40"), new Version("3.0")), - new PackageReference("testId2", VersionUtility.ParseFrameworkName("net40"), new Version("4.0")), - new PackageReference("testId3", VersionUtility.ParseFrameworkName("net40"), new Version("5.0")) - }; - - var installer = new PackageInstaller(provider.Object, logger.Object); - installer.InstallPackages(references, true); - - logger.Verify(i => i.Info(It.Is(x => x.EndsWith("successful."))), Times.Once()); + exception.ShouldBeType(); + ((AggregateException)exception).InnerExceptions.Count.ShouldEqual(1); } [Fact] public void ShouldNotInstallExistingPackages() { - var logger = new Mock(); var provider = new Mock(); provider.Setup( i => i.IsInstalled(It.Is(x => x.PackageId == "testId"), It.IsAny())) @@ -109,7 +77,7 @@ public void ShouldNotInstallExistingPackages() new PackageReference("testId3", VersionUtility.ParseFrameworkName("net40"), new Version("5.0")) }; - var installer = new PackageInstaller(provider.Object, logger.Object); + var installer = new PackageInstaller(provider.Object, new TestLogProvider()); installer.InstallPackages(references); provider.Verify(i => i.InstallPackage(It.Is(x => x.PackageId == "testId"), It.IsAny()), Times.Never()); diff --git a/test/ScriptCs.Hosting.Tests/Properties/AssemblyInfo.cs b/test/ScriptCs.Hosting.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index d1a57655..00000000 --- a/test/ScriptCs.Hosting.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("ScriptCs.Hosting.Tests")] -[assembly: AssemblyDescription("")] diff --git a/test/ScriptCs.Hosting.Tests/ReplCommands/OpenVSCommandTests.cs b/test/ScriptCs.Hosting.Tests/ReplCommands/OpenVSCommandTests.cs new file mode 100644 index 00000000..ef8f66f1 --- /dev/null +++ b/test/ScriptCs.Hosting.Tests/ReplCommands/OpenVSCommandTests.cs @@ -0,0 +1,67 @@ +using Moq; +using Moq.Protected; +using ScriptCs.Contracts; +using ScriptCs.Hosting.ReplCommands; +using System; +using System.Collections.Generic; +using Xunit; + +namespace ScriptCs.Hosting.Tests.ReplCommands +{ + public class OpenVsCommandTests + { + public class TheExecuteCommand + { + private Mock _mockCommand; + private OpenVsCommand _command; + private Mock _mockConsole; + private Mock _mockWriter; + private Mock _mockRepl; + + public TheExecuteCommand() + { + _mockConsole = new Mock(); + _mockWriter = new Mock(); + _mockRepl = new Mock(); + _mockRepl.SetupGet(r => r.FileSystem).Returns(new FileSystem()); + _mockCommand = new Mock(_mockConsole.Object, _mockWriter.Object); + _mockCommand.Setup(c => c.PlatformID).Returns(PlatformID.Win32NT); + _mockCommand.Setup(c => c.LaunchSolution(It.IsAny())); + _mockCommand.Protected(); + _command = _mockCommand.Object; + } + + [Fact] + public void OutputsAMessageIfNotWindows8() + { + _mockCommand.Setup(c => c.PlatformID).Returns(PlatformID.MacOSX); + _command.Execute(_mockRepl.Object, new object[] { "test.csx" }); + _mockConsole.Verify(c => c.WriteLine("Requires Windows 8 or later to run")); + } + + [Fact] + public void CreatesTheSolution() + { + _command.Execute(_mockRepl.Object, new object[] { "test.csx" }); + _mockWriter.Verify( + w => + w.WriteSolution(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny>())); + } + + [Fact] + public void LaunchesTheSolution() + { + _command.Execute(_mockRepl.Object, new object[] { "test.csx" }); + _mockCommand.Verify(c => c.LaunchSolution(It.IsAny())); + } + + [Fact] + public void LaunchesTheSolutionEmptyParameter() + { + _command.Execute(_mockRepl.Object, new object[] { }); + _mockCommand.Verify(c => c.LaunchSolution(It.IsAny())); + } + } + } +} diff --git a/test/ScriptCs.Hosting.Tests/RuntimeServicesTests.cs b/test/ScriptCs.Hosting.Tests/RuntimeServicesTests.cs index 8b2143db..b5b888c3 100644 --- a/test/ScriptCs.Hosting.Tests/RuntimeServicesTests.cs +++ b/test/ScriptCs.Hosting.Tests/RuntimeServicesTests.cs @@ -1,45 +1,59 @@ using System; using System.Collections.Generic; - +using System.IO; +using System.Linq; +using System.Reflection; using Autofac; -using Common.Logging; using Moq; using ScriptCs.Contracts; -using ScriptCs.Package; - +using ScriptCs.Tests; using Should; using Xunit; -namespace ScriptCs.Tests +namespace ScriptCs.Hosting.Tests { public class RuntimeServicesTests { - public class TheCreateContainerMethod + public class TheContainerProperty { - private Mock _mockConsole = new Mock(); - private Type _scriptExecutorType = null; - private Type _scriptEngineType = null; - private Mock _mockLogger = new Mock(); - private IDictionary _overrides = new Dictionary(); - private RuntimeServices _runtimeServices = null; - - public TheCreateContainerMethod() - { - var mockScriptExecutorType = new Mock(); - _scriptExecutorType = mockScriptExecutorType.Object.GetType(); - - var mockScriptEngineType = new Mock(); - _scriptEngineType = mockScriptEngineType.Object.GetType(); - - var initializationServices = new InitializationServices(_mockLogger.Object, _overrides); - _runtimeServices = new RuntimeServices(_mockLogger.Object, _overrides, new List(), _mockConsole.Object, _scriptEngineType, _scriptExecutorType, false, initializationServices, "script.csx"); + private readonly Mock _mockConsole = new Mock(); + private readonly Type _scriptExecutorType; + private readonly Type _replType; + private readonly Type _scriptEngineType; + private readonly TestLogProvider _logProvider = new TestLogProvider(); + private readonly IDictionary _overrides = new Dictionary(); + private readonly RuntimeServices _runtimeServices; + + public TheContainerProperty() + { + _overrides[typeof(ILineProcessor)] = new List(); + var mockScriptExecutor = new MockScriptExecutor(); + _scriptExecutorType = mockScriptExecutor.GetType(); + + var mockReplType = new Mock(); + _replType = mockReplType.Object.GetType(); + + var mockScriptEngine = new MockScriptEngine(); + _scriptEngineType = mockScriptEngine.GetType(); + + var initializationServices = new InitializationServices(_logProvider, _overrides); + _runtimeServices = new RuntimeServices( + _logProvider, + _overrides, + _mockConsole.Object, + _scriptEngineType, + _scriptExecutorType, + _replType, + false, + initializationServices, + "script.csx"); } [Fact] public void ShouldRegisterTheLoggerInstance() { - var logger = _runtimeServices.Container.Resolve(); - logger.ShouldEqual(_mockLogger.Object); + var logProvider = _runtimeServices.Container.Resolve(); + logProvider.ShouldEqual(_logProvider); } [Fact] @@ -59,214 +73,786 @@ public void ShouldRegisterTheExecutor() [Fact] public void ShouldRegisterTheConsoleInstance() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheScriptServices() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultScriptHostFactoryIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultFilePreProcessorIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultScriptPackResolverIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultInstallationProviderIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultPackageInstallerIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultScriptServiceRootIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultFileSystemIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultAssemblyUtilityIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultPackageContainerIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultPackageAssemblyResolverIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); } [Fact] public void ShouldRegisterTheDefaultAssemblyResolverIfNoOverride() { - _runtimeServices.Container.Resolve().ShouldNotBeNull(); + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); + } + + [Fact] + public void ShouldRegisterTheDefaultVisualStudioSolutionWriterIfNoOverride() + { + _runtimeServices.Container.IsRegistered().ShouldBeTrue(); + } + + [Fact] + public void ShouldRegisterTheDefaultLineProcessors() + { + var processors = _runtimeServices.Container.Resolve>(); + processors.ShouldNotBeNull(); + processors = processors.ToArray(); + processors.Where(p => p is IUsingLineProcessor).ShouldNotBeEmpty(); + processors.Where(p => p is IReferenceLineProcessor).ShouldNotBeEmpty(); + processors.Where(p => p is ILoadLineProcessor).ShouldNotBeEmpty(); + processors.Where(p => p is IShebangLineProcessor).ShouldNotBeEmpty(); + } + + [Fact] + public void ShouldRegisterACustomLineProcessor() + { + var processorList = _overrides[typeof(ILineProcessor)] as List; + processorList.ShouldNotBeNull(); + processorList.Add(typeof(MockLineProcessor)); + + var processors = _runtimeServices.Container.Resolve>(); + processors.ShouldNotBeNull(); + processors.Where(p => p.GetType() == typeof(MockLineProcessor)).ShouldNotBeEmpty(); } [Fact] public void ShouldRegisterTheOverriddenScriptHostFactory() { - var mock = new Mock(); - _overrides[typeof(IScriptHostFactory)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IScriptHostFactory)] = typeof(MockScriptHostFactory); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockScriptHostFactory)); } [Fact] public void ShouldRegisterTheOverriddenFilePreProcessor() { var mock = new Mock(); - _overrides[typeof(IFilePreProcessor)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IFilePreProcessor)] = typeof(MockFilePreProcessor); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockFilePreProcessor)); } [Fact] public void ShouldRegisterTheOverriddenScriptPackResolver() { - var mock = new Mock(); - _overrides[typeof(IScriptPackResolver)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IScriptPackResolver)] = typeof(MockScriptPackResolver); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockScriptPackResolver)); } [Fact] public void ShouldRegisterTheOverriddenInstallationProvider() { - var mock = new Mock(); - _overrides[typeof(IInstallationProvider)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IInstallationProvider)] = typeof(MockInstallationProvider); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockInstallationProvider)); } [Fact] public void ShouldRegisterTheOverriddenPackageInstaller() { - var mock = new Mock(); - _overrides[typeof(IPackageInstaller)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IPackageInstaller)] = typeof(MockPackageInstaller); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockPackageInstaller)); } [Fact] public void ShouldRegisterTheOverriddenFileSystem() { - var mock = new Mock(); - _overrides[typeof(IFileSystem)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IFileSystem)] = typeof(MockFileSystem); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockFileSystem)); } [Fact] public void ShouldRegisterTheOverriddenAssemblyUtility() { - var mock = new Mock(); - _overrides[typeof(IAssemblyUtility)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IAssemblyUtility)] = typeof(MockAssemblyUtility); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockAssemblyUtility)); } [Fact] public void ShouldRegisterTheOverriddenConsole() { - var mock = new Mock(); - _overrides[typeof(IConsole)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IConsole)] = typeof(MockConsole); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockConsole)); } [Fact] public void ShouldRegisterTheOverriddenPackageContainer() { - var mock = new Mock(); - _overrides[typeof(IPackageContainer)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IPackageContainer)] = typeof(MockPackageContainer); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockPackageContainer)); } [Fact] public void ShouldRegisterTheOverriddenPackageAssemblyResolver() { - var mock = new Mock(); - _overrides[typeof(IPackageAssemblyResolver)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IPackageAssemblyResolver)] = typeof(MockPackageAssemblyResolver); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockPackageAssemblyResolver)); } [Fact] public void ShouldRegisterTheOverriddenAssemblyResolver() { - var mock = new Mock(); - _overrides[typeof(IAssemblyResolver)] = mock.Object.GetType(); - _runtimeServices.Container.Resolve().ShouldBeType(mock.Object.GetType()); + _overrides[typeof(IAssemblyResolver)] = typeof(MockAssemblyResolver); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockAssemblyResolver)); } [Fact] public void ShouldRegisterTheOverriddenAssemblyResolverInstance() { - var mock = new Mock(); - _overrides[typeof(IAssemblyResolver)] = mock.Object; - _runtimeServices.Container.Resolve().ShouldEqual(mock.Object); + var mock = new MockAssemblyResolver(); + _overrides[typeof(IAssemblyResolver)] = mock; + _runtimeServices.Container.Resolve().ShouldEqual(mock); + } + + [Fact] + public void ShouldRegisterTheOverriddenVisualStudioSolutionWriter() + { + _overrides[typeof(IVisualStudioSolutionWriter)] = typeof(MockVisualStudioSolutionWriter); + _runtimeServices.Container.Resolve().ShouldBeType(typeof(MockVisualStudioSolutionWriter)); + } + + [Fact] + public void ShouldRegisterTheOverriddenRepl() + { + var mock = new Mock(); + _overrides[ typeof( IRepl ) ] = mock.Object.GetType(); + _runtimeServices.Container.Resolve().ShouldBeType( mock.Object.GetType() ); } [Fact] public void ShouldLogOnDebugAnAssemblyLoadFailure() { - var mock = new Mock(); - mock.Setup(a => a.GetAssemblyPaths(It.IsAny())).Returns(new[] {"foo.dll"}); - _overrides[typeof (IAssemblyResolver)] = mock.Object; - var initializationServices = new InitializationServices(_mockLogger.Object, _overrides); - var runtimeServices = new RuntimeServices(_mockLogger.Object, _overrides, new List(), _mockConsole.Object, _scriptEngineType, _scriptExecutorType, true, initializationServices, "script.csx"); + // arrange + var mockResolver = new Mock(); + mockResolver.Setup(a => a.GetAssemblyPaths(It.IsAny(), false)).Returns(new[] { "/foo.dll" }); + _overrides[typeof(IAssemblyResolver)] = mockResolver.Object; + + var mockAssemblyUtility = new Mock(); + mockAssemblyUtility.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); + _overrides[typeof(IAssemblyUtility)] = mockAssemblyUtility.Object; + + var initializationServices = new InitializationServices(_logProvider, _overrides); + var runtimeServices = new RuntimeServices( + _logProvider, + _overrides, + _mockConsole.Object, + _scriptEngineType, + _scriptExecutorType, + _replType, + true, + initializationServices, + "script.csx"); + + // act var container = runtimeServices.Container; - _mockLogger.Verify(l=>l.DebugFormat("Failure loading assembly: {0}. Exception: {1}", "foo.dll", "Could not load file or assembly 'foo.dll' or one of its dependencies. The system cannot find the file specified.")); + + // assert + _logProvider.Output.ShouldContain( + "DEBUG: Failure loading assembly: /foo.dll. Exception: Could not load file or assembly 'foo.dll' or one of its dependencies. The system cannot find the file specified."); } [Fact] public void ShouldLogAGeneralWarningOnAnAssemblyLoadFailureWhenRunningScript() { - var mock = new Mock(); - mock.Setup(a => a.GetAssemblyPaths(It.IsAny())).Returns(new[] { "foo.dll" }); - _overrides[typeof(IAssemblyResolver)] = mock.Object; - var initializationServices = new InitializationServices(_mockLogger.Object, _overrides); - var runtimeServices = new RuntimeServices(_mockLogger.Object, _overrides, new List(), _mockConsole.Object, _scriptEngineType, _scriptExecutorType, true, initializationServices, "script.csx"); + // arrange + var mockResolver = new Mock(); + mockResolver.Setup(a => a.GetAssemblyPaths(It.IsAny(), false)).Returns(new[] { "/foo.dll" }); + _overrides[typeof(IAssemblyResolver)] = mockResolver.Object; + + var mockAssemblyUtility = new Mock(); + mockAssemblyUtility.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); + _overrides[typeof(IAssemblyUtility)] = mockAssemblyUtility.Object; + + var initializationServices = new InitializationServices(_logProvider, _overrides); + var runtimeServices = new RuntimeServices( + _logProvider, + _overrides, + _mockConsole.Object, + _scriptEngineType, + _scriptExecutorType, + _replType, + true, + initializationServices, + "script.csx"); + + // act var container = runtimeServices.Container; - _mockLogger.Verify(l => l.Warn("Some assemblies failed to load. Launch with '-loglevel debug' to see the details")); + + // assert + _logProvider.Output.ShouldContain( + "WARN: Some assemblies failed to load. Launch with '-loglevel debug' to see the details"); } [Fact] public void ShouldLogAGeneralWarningOnAnAssemblyLoadFailureWhenRunningInRepl() { - var mock = new Mock(); - mock.Setup(a => a.GetAssemblyPaths(It.IsAny())).Returns(new[] { "foo.dll" }); - _overrides[typeof(IAssemblyResolver)] = mock.Object; - var initializationServices = new InitializationServices(_mockLogger.Object, _overrides); - var runtimeServices = new RuntimeServices(_mockLogger.Object, _overrides, new List(), _mockConsole.Object, _scriptEngineType, _scriptExecutorType, true, initializationServices, ""); + // arrange + var mockResolver = new Mock(); + mockResolver.Setup(a => a.GetAssemblyPaths(It.IsAny(), false)).Returns(new[] { "/foo.dll" }); + _overrides[typeof(IAssemblyResolver)] = mockResolver.Object; + + var mockAssemblyUtility = new Mock(); + mockAssemblyUtility.Setup(a => a.IsManagedAssembly(It.IsAny())).Returns(true); + _overrides[typeof (IAssemblyUtility)] = mockAssemblyUtility.Object; + + var initializationServices = new InitializationServices(_logProvider, _overrides); + var runtimeServices = new RuntimeServices( + _logProvider, + _overrides, + _mockConsole.Object, + _scriptEngineType, + _scriptExecutorType, + _replType, true, + initializationServices, + ""); + + // act var container = runtimeServices.Container; - _mockLogger.Verify(l => l.Warn("Some assemblies failed to load. Launch with '-repl -loglevel debug' to see the details")); + + // assert + _logProvider.Output.ShouldContain( + "WARN: Some assemblies failed to load. Launch with '-repl -loglevel debug' to see the details"); + } + + [Fact] + public void ShouldResolveAssembliesBasedOnScriptWorkingDirectory() + { + // arrange + var fsmock = new Mock(); + fsmock.Setup(a => a.GetWorkingDirectory(It.IsAny())).Returns("c:/scripts"); + + var resolvermock = new Mock(); + resolvermock.Setup(a => a.GetAssemblyPaths("c:/scripts", false)).Returns(new[] { "foo.dll" }); + + _overrides[typeof(IFileSystem)] = fsmock.Object; + _overrides[typeof(IAssemblyResolver)] = resolvermock.Object; + + var initializationServices = new InitializationServices(_logProvider, _overrides); + var runtimeServices = new RuntimeServices( + _logProvider, + _overrides, + _mockConsole.Object, + _scriptEngineType, + _scriptExecutorType, + _replType, + true, + initializationServices, + "c:/scriptcs/script.csx"); + + // act + var container = runtimeServices.Container; + + // assert + resolvermock.Verify(x => x.GetAssemblyPaths("c:/scripts", false), Times.Exactly(1)); + } + + private class MockScriptEngine : IScriptEngine + { + public string BaseDirectory { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string CacheDirectory { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string FileName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public ScriptResult Execute(string code, string[] scriptArgs, AssemblyReferences references, IEnumerable namespaces, ScriptPackSession scriptPackSession) + { + throw new NotImplementedException(); + } + } + + private class MockScriptExecutor : IScriptExecutor + { + public AssemblyReferences References => throw new NotImplementedException(); + + public IReadOnlyCollection Namespaces => throw new NotImplementedException(); + + public IScriptEngine ScriptEngine => throw new NotImplementedException(); + + public IFileSystem FileSystem => throw new NotImplementedException(); + + public ScriptPackSession ScriptPackSession => throw new NotImplementedException(); + + public void AddReferences(params Assembly[] references) + { + throw new NotImplementedException(); + } + + public void AddReferences(params string[] references) + { + throw new NotImplementedException(); + } + + public ScriptResult Execute(string script, params string[] scriptArgs) + { + throw new NotImplementedException(); + } + + public ScriptResult ExecuteScript(string script, params string[] scriptArgs) + { + throw new NotImplementedException(); + } + + public void ImportNamespaces(params string[] namespaces) + { + throw new NotImplementedException(); + } + + public void Initialize(IEnumerable paths, IEnumerable scriptPacks, params string[] scriptArgs) + { + throw new NotImplementedException(); + } + + public void RemoveNamespaces(params string[] namespaces) + { + throw new NotImplementedException(); + } + + public void RemoveReferences(params Assembly[] references) + { + throw new NotImplementedException(); + } + + public void RemoveReferences(params string[] references) + { + throw new NotImplementedException(); + } + + public void Reset() + { + throw new NotImplementedException(); + } + + public void Terminate() + { + throw new NotImplementedException(); + } + } + + private class MockFileSystem : IFileSystem + { + public IEnumerable EnumerateFiles( + string dir, string search, SearchOption searchOption = SearchOption.AllDirectories) + { + throw new NotImplementedException(); + } + + public IEnumerable EnumerateDirectories( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) + { + throw new NotImplementedException(); + } + + public IEnumerable EnumerateFilesAndDirectories( + string dir, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) + { + throw new NotImplementedException(); + } + + public void Copy(string source, string dest, bool overwrite) + { + throw new NotImplementedException(); + } + + public void CopyDirectory(string source, string dest, bool overwrite) + { + throw new NotImplementedException(); + } + + public bool DirectoryExists(string path) + { + throw new NotImplementedException(); + } + + public void CreateDirectory(string path, bool hidden = false) + { + throw new NotImplementedException(); + } + + public void DeleteDirectory(string path) + { + throw new NotImplementedException(); + } + + public string ReadFile(string path) + { + throw new NotImplementedException(); + } + + public string[] ReadFileLines(string path) + { + throw new NotImplementedException(); + } + + public DateTime GetLastWriteTime(string file) + { + throw new NotImplementedException(); + } + + public bool IsPathRooted(string path) + { + throw new NotImplementedException(); + } + + public string GetFullPath(string path) + { + throw new NotImplementedException(); + } + + public string TempPath { get; private set; } + + public string CurrentDirectory + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public string NewLine + { + get { throw new NotImplementedException(); } + } + + public string GetWorkingDirectory(string path) + { + throw new NotImplementedException(); + } + + public void Move(string source, string dest) + { + throw new NotImplementedException(); + } + + public void MoveDirectory(string source, string dest) + { + throw new NotImplementedException(); + } + + public bool FileExists(string path) + { + throw new NotImplementedException(); + } + + public void FileDelete(string path) + { + throw new NotImplementedException(); + } + + public IEnumerable SplitLines(string value) + { + throw new NotImplementedException(); + } + + public void WriteToFile(string path, string text) + { + throw new NotImplementedException(); + } + + public Stream CreateFileStream(string filePath, FileMode mode) + { + throw new NotImplementedException(); + } + + public void WriteAllBytes(string filePath, byte[] bytes) + { + throw new NotImplementedException(); + } + + public string GlobalFolder + { + get { throw new NotImplementedException(); } + } + + public string HostBin + { + get { throw new NotImplementedException(); } + } + + public string BinFolder + { + get { return "bin"; } + } + + public string DllCacheFolder + { + get { throw new NotImplementedException(); } + } + + public string PackagesFile + { + get { return "packages.config"; } + } + + public string PackagesFolder + { + get { return "packages"; } + } + + public string NugetFile + { + get { throw new NotImplementedException(); } + } + + public string GlobalOptsFile + { + get { throw new NotImplementedException(); } + } + + public string PackageScriptsFile + { + get { throw new NotImplementedException(); } + } + } + + private class MockLineProcessor : ILineProcessor + { + public bool ProcessLine(IFileParser parser, FileParserContext context, string line, bool isBeforeCode) + { + throw new NotImplementedException(); + } + } + + private class MockScriptHostFactory : IScriptHostFactory + { + public IScriptHost CreateScriptHost(IScriptPackManager scriptPackManager, string[] scriptArgs) + { + throw new NotImplementedException(); + } + } + + private class MockFilePreProcessor : IFilePreProcessor + { + public void ParseFile(string path, FileParserContext context) + { + throw new NotImplementedException(); + } + + public void ParseScript(List scriptLines, FileParserContext context) + { + throw new NotImplementedException(); + } + + public FilePreProcessorResult ProcessFile(string path) + { + throw new NotImplementedException(); + } + + public FilePreProcessorResult ProcessScript(string script) + { + throw new NotImplementedException(); + } } + private class MockScriptPackResolver : IScriptPackResolver + { + public IEnumerable GetPacks() + { + throw new NotImplementedException(); + } + } + + private class MockInstallationProvider : IInstallationProvider + { + public IEnumerable GetRepositorySources(string path) + { + throw new NotImplementedException(); + } + + public void Initialize() + { + throw new NotImplementedException(); + } + + public void InstallPackage(IPackageReference packageId, bool allowPreRelease = false) + { + throw new NotImplementedException(); + } + + public bool IsInstalled(IPackageReference packageId, bool allowPreRelease = false) + { + throw new NotImplementedException(); + } + } + + private class MockPackageInstaller : IPackageInstaller + { + public void InstallPackages(IEnumerable packageIds, bool allowPreRelease = false) + { + throw new NotImplementedException(); + } + } + + private class MockAssemblyUtility : IAssemblyUtility + { + public AssemblyName GetAssemblyName(string path) + { + throw new NotImplementedException(); + } + + public bool IsManagedAssembly(string path) + { + throw new NotImplementedException(); + } + public Assembly Load(AssemblyName assemblyRef) + { + throw new NotImplementedException(); + } + + public Assembly LoadFile(string path) + { + throw new NotImplementedException(); + } + } + + private class MockConsole : IConsole + { + public ConsoleColor ForegroundColor { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public int Width => throw new NotImplementedException(); + + public void Clear() + { + throw new NotImplementedException(); + } + + public void Exit() + { + throw new NotImplementedException(); + } + + public string ReadLine(string prompt) + { + throw new NotImplementedException(); + } + + public void ResetColor() + { + throw new NotImplementedException(); + } + + public void Write(string value) + { + throw new NotImplementedException(); + } + + public void WriteLine() + { + throw new NotImplementedException(); + } + + public void WriteLine(string value) + { + throw new NotImplementedException(); + } + } + + private class MockPackageContainer : IPackageContainer + { + public void CreatePackageFile() + { + throw new NotImplementedException(); + } + + public IPackageObject FindPackage(string path, IPackageReference packageReference) + { + throw new NotImplementedException(); + } + + public IEnumerable FindReferences(string path) + { + throw new NotImplementedException(); + } + } + + private class MockPackageAssemblyResolver : IPackageAssemblyResolver + { + public IEnumerable GetAssemblyNames(string workingDirectory) + { + throw new NotImplementedException(); + } + + public IEnumerable GetPackages(string workingDirectory) + { + throw new NotImplementedException(); + } + + public void SavePackages() + { + throw new NotImplementedException(); + } + } + + private class MockAssemblyResolver : IAssemblyResolver + { + public IEnumerable GetAssemblyPaths(string path, bool binariesOnly = false) + { + throw new NotImplementedException(); + } + } + private class MockVisualStudioSolutionWriter : IVisualStudioSolutionWriter + { + public string WriteSolution(IFileSystem fs, string script, IVisualStudioSolution solution, IList nestedItems = null) + { + throw new NotImplementedException(); + } + } } } } diff --git a/test/ScriptCs.Hosting.Tests/ScriptCs.Hosting.Tests.csproj b/test/ScriptCs.Hosting.Tests/ScriptCs.Hosting.Tests.csproj index c224c743..cb5cc998 100644 --- a/test/ScriptCs.Hosting.Tests/ScriptCs.Hosting.Tests.csproj +++ b/test/ScriptCs.Hosting.Tests/ScriptCs.Hosting.Tests.csproj @@ -1,98 +1,30 @@ - - - + - {EC03EAA0-94FD-4145-8F78-5F2E30E3FF9A} - Library - Properties - ScriptCs.Hosting.Tests - ScriptCs.Hosting.Tests - ..\..\ - ..\..\ScriptCs.Test.ruleset + net461 - - False - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.dll - - - ..\..\packages\Autofac.3.0.2\lib\net40\Autofac.Configuration.dll - - - False - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - False - ..\..\packages\Microsoft.Web.Xdt.1.0.0\lib\net40\Microsoft.Web.XmlTransform.dll - - - ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - True - - - False - ..\..\packages\NuGet.Core.2.7.0\lib\net40-Client\NuGet.Core.dll - - - ..\..\packages\AutoFixture.3.6.5\lib\net40\Ploeh.AutoFixture.dll - - - ..\..\packages\AutoFixture.AutoMoq.3.6.5\lib\net40\Ploeh.AutoFixture.AutoMoq.dll - - - ..\..\packages\AutoFixture.Xunit.3.6.5\lib\net40\Ploeh.AutoFixture.Xunit.dll - - - False - ..\..\packages\Should.1.1.20\lib\Should.dll - - - - - - - ..\..\packages\xunit.1.9.1\lib\net20\xunit.dll - - - ..\..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll - + + + + + + + + + + + + - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - ScriptCsAutoDataAttribute.cs - - - - - - - + + + - - + - - {6049e205-8b5f-4080-b023-70600e51fd64} - ScriptCs.Contracts - - - {E590E710-E159-48E6-A3E6-1A83D3FE732C} - ScriptCs.Core - - - {9aef2d95-87fb-4829-b384-34bfe076d531} - ScriptCs.Hosting - + - - \ No newline at end of file diff --git a/test/ScriptCs.Hosting.Tests/ScriptServicesBuilderTests.cs b/test/ScriptCs.Hosting.Tests/ScriptServicesBuilderTests.cs index 879e5a34..ed54c105 100644 --- a/test/ScriptCs.Hosting.Tests/ScriptServicesBuilderTests.cs +++ b/test/ScriptCs.Hosting.Tests/ScriptServicesBuilderTests.cs @@ -1,35 +1,200 @@ -using Common.Logging; +using System; +using System.Collections.Generic; +using System.Linq; using Moq; using ScriptCs.Contracts; +using ScriptCs.Tests; using Should; -using ScriptCs.Package; - using Xunit; +using AutoFixture.Xunit2; -namespace ScriptCs.Tests +namespace ScriptCs.Hosting.Tests { public class ScriptServicesBuilderTests { public class TheBuildMethod { - private Mock _mockLogger = new Mock(); + [Theory, ScriptCsAutoData] + public void ShouldResolveScriptServices(ScriptServices scriptServices, [Frozen] Mock runtimeServicesMock, ScriptServicesBuilder builder) + { + runtimeServicesMock.Setup(r => r.GetScriptServices()).Returns(scriptServices); + builder.Overrides[typeof(IScriptEngine)] = null; + builder.Build().ShouldEqual(scriptServices); + } + + [Theory, ScriptCsAutoData] + public void ShouldLoadScriptPacksIfReplIsTrue(IConsole console, TestLogProvider logProvider) + { + var builder = new ScriptServicesBuilder(console, logProvider); + builder.Overrides[typeof(IScriptEngine)] = typeof(MockScriptEngine); + builder.Repl(); + builder.Build(); + var runtimeServices = (RuntimeServices) builder.RuntimeServices; + runtimeServices.InitDirectoryCatalog.ShouldBeTrue(); + } + + [Theory, ScriptCsAutoData] + public void ShouldLoadScriptPacksIfScriptNameIsSet(IConsole console, TestLogProvider logProvider) + { + var builder = new ScriptServicesBuilder(console, logProvider); + builder.Overrides[typeof(IScriptEngine)] = typeof(MockScriptEngine); + builder.ScriptName(""); + builder.Build(); + var runtimeServices = (RuntimeServices)builder.RuntimeServices; + runtimeServices.InitDirectoryCatalog.ShouldBeTrue(); + } + + [Theory, ScriptCsAutoData] + public void ShoulLoadScriptPacksIfLoadScriptPacksIsTrue(IConsole console, TestLogProvider logProvider) + { + var builder = new ScriptServicesBuilder(console, logProvider); + builder.Overrides[typeof(IScriptEngine)] = typeof(MockScriptEngine); + builder.LoadScriptPacks(); + builder.Build(); + var runtimeServices = (RuntimeServices)builder.RuntimeServices; + runtimeServices.InitDirectoryCatalog.ShouldBeTrue(); + } + + [Theory, ScriptCsAutoData] + public void ShouldNotLoadScriptPacksIfLoadScriptPacksIsFalse(IConsole console, TestLogProvider logProvider) + { + var builder = new ScriptServicesBuilder(console, logProvider); + builder.Overrides[typeof(IScriptEngine)] = typeof(MockScriptEngine); + builder.LoadScriptPacks(false); + builder.Build(); + var runtimeServices = (RuntimeServices)builder.RuntimeServices; + runtimeServices.InitDirectoryCatalog.ShouldBeFalse(); + } + + private class MockScriptEngine : IScriptEngine + { + public string BaseDirectory { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string CacheDirectory { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string FileName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public ScriptResult Execute(string code, string[] scriptArgs, AssemblyReferences references, IEnumerable namespaces, ScriptPackSession scriptPackSession) + { + throw new NotImplementedException(); + } + } + } + + public class TheLoadModulesMethod + { + [Theory, ScriptCsAutoData] + public void ShouldLoadTheMonoModuleWhenTheMonoRuntimeIsPresent([Frozen] Mock typeResolver, [Frozen] Mock moduleLoader, ScriptServicesBuilder builder) + { + typeResolver.Setup(r => r.ResolveType("Mono.Runtime")).Returns(typeof(string)); + moduleLoader.Setup( + m => + m.Load(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Callback( + (config, paths, hostBin, extension, module) => module.Single().ShouldEqual("mono")); + builder.LoadModules(null); + } + + [Theory, ScriptCsAutoData] + public void ShouldLoadTheMonoModuleWhenTheMonoModuleIsPassedInTheListOfModules([Frozen] Mock typeResolver, [Frozen] Mock moduleLoader, ScriptServicesBuilder builder) + { + typeResolver.Setup(r => r.ResolveType("Mono.Runtime")).Returns((Type)null); + moduleLoader.Setup( + m => + m.Load(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Callback( + (config, paths, hostBin, extension, module) => module.Single().ShouldEqual("mono")); + builder.LoadModules(null, "mono"); + } + + [Theory, ScriptCsAutoData] + public void ShouldLoadTheRoslynModuleWhenTheMonoModuleIsNotSelected([Frozen] Mock typeResolver, [Frozen] Mock moduleLoader, ScriptServicesBuilder builder) + { + typeResolver.Setup(r => r.ResolveType("Mono.Runtime")).Returns((Type)null); + moduleLoader.Setup( + m => + m.Load(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Callback( + (config, paths, hostBin, extension, module) => module.Single().ShouldEqual("roslyn")); + builder.LoadModules(null); + + } + + [Theory, ScriptCsAutoData] + public void ShouldFindAllModulesInTheFileSystem([Frozen] Mock typeResolver, [Frozen] Mock moduleLoader, [Frozen] Mock fileSystem, [Frozen] Mock initializationServices) + { + typeResolver.Setup(r => r.ResolveType("Mono.Runtime")).Returns((Type)null); + fileSystem.SetupGet(fs => fs.GlobalFolder).Returns(@"c:\modules"); + fileSystem.SetupGet(fs => fs.GlobalFolder).Returns(@"c:\current"); + fileSystem.SetupGet(fs => fs.HostBin).Returns(@"c:\hostbin"); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + moduleLoader.Setup( + m => + m.Load(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Callback( + (config, paths, hostBin, extension, module) => + { + paths.ShouldContain(@"c:\modules"); + paths.ShouldContain(@"c:\current"); + paths.ShouldContain(@"c:\hostbin"); + }); + + } + } + + public class TheSetOverrideMethods + { + [Theory, ScriptCsAutoData] + public void ShouldReturnTheBuilder(ScriptServicesBuilder builder) + { + var someValue = new SomeOverride(); + var returnedBuilder = builder.SetOverride(someValue); + + returnedBuilder.ShouldBeSameAs(builder); + } + + [Theory, ScriptCsAutoData] + public void ShouldSetTheValueUsingTheKey(ScriptServicesBuilder builder) + { + var someValue = new SomeOverride(); + var key = typeof(ISomeOverride); + builder.SetOverride(someValue); - private ScriptServices _scriptServices = new ScriptServices(null, null, null, null, null, null, null, null, null, null); - private Mock _mockFactory = new Mock(); - private Mock _mockConsole = new Mock(); - private ScriptServicesBuilder _builder = null; + var overrides = builder.Overrides; + overrides.ContainsKey(key).ShouldBeTrue(); + overrides[key].ShouldBeSameAs(someValue); + } - public TheBuildMethod() + [Theory, ScriptCsAutoData] + public void ShouldReplaceTheValueWhenKeyAlreadyExists(ScriptServicesBuilder builder) { - _mockFactory.Setup(f => f.GetScriptServices()).Returns(_scriptServices); - _builder = new ScriptServicesBuilder(_mockConsole.Object, _mockLogger.Object, _mockFactory.Object); + var key = typeof(ISomeOverride); + var firstValue = new SomeOverride(); + var secondValue = new SomeOverride(); + + builder.SetOverride(firstValue); + builder.SetOverride(secondValue); + + var overrides = builder.Overrides; + overrides[key].ShouldNotBeSameAs(firstValue); + overrides[key].ShouldBeSameAs(secondValue); } - [Fact] - public void ShouldResolveScriptServices() + [Theory, ScriptCsAutoData] + public void ShouldUseTheValueTypeWhenNoInstanceIsProvided(ScriptServicesBuilder builder) { - _builder.Build().ShouldEqual(_scriptServices); + var key = typeof(ISomeOverride); + builder.SetOverride(); + + var overrides = builder.Overrides; + overrides.ContainsKey(key).ShouldBeTrue(); + overrides[key].ShouldBeSameAs(typeof(SomeOverride)); } + + public interface ISomeOverride { } + public class SomeOverride : ISomeOverride { } } } } diff --git a/test/ScriptCs.Hosting.Tests/VisualStudioSolutionTests.cs b/test/ScriptCs.Hosting.Tests/VisualStudioSolutionTests.cs new file mode 100644 index 00000000..6114bc92 --- /dev/null +++ b/test/ScriptCs.Hosting.Tests/VisualStudioSolutionTests.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Should; +using Xunit; +using System.IO; +using ScriptCs.Contracts; + +namespace ScriptCs.Hosting.Tests +{ + public class VisualStudioSolutionTests + { + public class TheConstructor + { + private VisualStudioSolution _solution = new VisualStudioSolution(); + + [Fact] + public void ShouldInitializeVariables() + { + _solution.Header.ShouldNotBeNull(); + _solution.Projects.ShouldNotBeNull(); + _solution.Global.ShouldNotBeNull(); + } + + [Fact] + public void ShouldAppendTheHeader() + { + var headerBuilder = new StringBuilder(); + headerBuilder.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); + headerBuilder.AppendLine("# Visual Studio 2013"); + headerBuilder.AppendLine("VisualStudioVersion = 12.0.30501.0"); + headerBuilder.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); + _solution.Header.ToString().ShouldEqual(headerBuilder.ToString()); + } + } + + public class TheAddGlobalHeaderMethod + { + private Guid _projectGuid = Guid.NewGuid(); + private VisualStudioSolution _builder = new VisualStudioSolution(); + + [Fact] + public void ShouldAppendTheGlobalHeader() + { + _builder.AddGlobalHeader(_projectGuid); + var globalBuilder = new StringBuilder(); + globalBuilder.AppendLine("Global"); + globalBuilder.AppendLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution"); + globalBuilder.AppendLine("\t\tDebug|Any CPU = Debug|Any CPU"); + globalBuilder.AppendLine("\tEndGlobalSection"); + globalBuilder.AppendLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution"); + globalBuilder.AppendFormat("\t\t{{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU{1}", _projectGuid, Environment.NewLine); + globalBuilder.AppendLine("\tEndGlobalSection"); + globalBuilder.AppendLine("\tGlobalSection(SolutionProperties) = preSolution"); + globalBuilder.AppendLine("\t\tHideSolutionNode = FALSE"); + globalBuilder.AppendLine("\tEndGlobalSection"); + globalBuilder.ToString().ShouldEqual(_builder.Global.ToString()); + } + } + + public class TheAddGlobalNestedProjectsMethod + { + [Fact] + public void ShouldAppenedGlobalSectionEntriesForEachProject() + { + var builder = new VisualStudioSolution(); + + var nestedItems = new List(); + var a = Guid.NewGuid(); + var b = Guid.NewGuid(); + var c = Guid.NewGuid(); + + nestedItems.Add(new ProjectItem(a, b)); + nestedItems.Add(new ProjectItem(b, c)); + builder.AddGlobalNestedProjects(nestedItems); + var nestedBuilder = new StringBuilder(); + nestedBuilder.AppendLine("\tGlobalSection(NestedProjects) = preSolution"); + nestedBuilder.AppendFormat("\t\t{{{0}}} = {{{1}}}{2}", nestedItems[0].Project, nestedItems[0].Parent, + Environment.NewLine); + nestedBuilder.AppendFormat("\t\t{{{0}}} = {{{1}}}{2}", nestedItems[1].Project, nestedItems[1].Parent, + Environment.NewLine); + nestedBuilder.AppendLine("\tEndGlobalSection"); + builder.Global.ToString().Contains(nestedBuilder.ToString()); + } + } + + public class TheAddScriptcsProjectMethod + { + private VisualStudioSolution _builder = new VisualStudioSolution(); + private const string _scriptcsPath = "scriptcs.exe"; + private const string _workingPath = "working"; + private const string _args = "test.csx"; + private const bool _attach = true; + private Guid _projectGuid = Guid.NewGuid(); + private string _projects; + + public TheAddScriptcsProjectMethod() + { + _builder.AddScriptcsProject(_scriptcsPath, _workingPath, _args, _attach, _projectGuid); + _projects = _builder.Projects.ToString(); + } + + [Fact] + public void ShouldAppendTheScriptcsProjectSection() + { + var projectBuilder = new StringBuilder(); + + projectBuilder.AppendFormat(@"Project(""{{911E67C6-3D85-4FCE-B560-20A9C3E3FF48}}"") = ""scriptcs"", ""{0}"", ""{{{1}}}""{2}", _scriptcsPath, _projectGuid, Environment.NewLine); + projectBuilder.AppendLine("\tProjectSection(DebuggerProjectSystem) = preProject"); + projectBuilder.AppendLine("\t\tPortSupplier = 00000000-0000-0000-0000-000000000000"); + projectBuilder.AppendFormat("\t\tExecutable = {0}{1}", _scriptcsPath, Environment.NewLine); + projectBuilder.AppendLine("\t\tRemoteMachine = localhost"); + projectBuilder.AppendFormat("\t\tStartingDirectory = {0}{1}", _workingPath, Environment.NewLine); + projectBuilder.AppendFormat("\t\tArguments = {0}{1}", _args, Environment.NewLine); + projectBuilder.AppendLine("\t\tEnvironment = Default"); + projectBuilder.AppendLine("\t\tLaunchingEngine = 00000000-0000-0000-0000-000000000000"); + projectBuilder.AppendLine("\t\tUseLegacyDebugEngines = No"); + projectBuilder.AppendLine("\t\tLaunchSQLEngine = No"); + projectBuilder.AppendFormat("\t\tAttachLaunchAction = {0}{1}", _attach ? "Yes" : "No", Environment.NewLine); + projectBuilder.AppendLine("\tEndProjectSection"); + projectBuilder.AppendLine("EndProject"); + _projects.ShouldEqual(projectBuilder.ToString()); + } + + [Fact] + public void ShouldSetTheProjectElementScriptcsPath() + { + _projects.Contains(string.Format(@"Project(""{{911E67C6-3D85-4FCE-B560-20A9C3E3FF48}}"") = ""scriptcs"", ""{0}""", _scriptcsPath)); + } + + [Fact] + public void ShouldSetTheExecutable() + { + _projects.Contains(string.Format("Executable = {0}", _scriptcsPath)); + } + + [Fact] + public void ShouldSetTheStartingDirectory() + { + _projects.Contains(string.Format("StartingDirectory = {0}", _workingPath)); + } + + [Fact] + public void ShouldSetTheArguements() + { + _projects.Contains(string.Format("Arguments = {0}", _args)); + } + + [Fact] + public void ShouldSetTheAttachAction() + { + _projects.Contains(string.Format("AttachLaunchAction = {0}", _attach ? "Yes" : "No")); + } + } + + public class TheToStringMethod + { + [Fact] + public void BuildsTheSolution() + { + var builder = new VisualStudioSolution(); + builder.Header = new StringBuilder("A"); + builder.Projects = new StringBuilder("B"); + builder.Global = new StringBuilder("C"); + var solution = builder.ToString(); + solution.ShouldEqual("ABC"); + } + } + } +} diff --git a/test/ScriptCs.Hosting.Tests/VisualStudioSolutionWriterTests.cs b/test/ScriptCs.Hosting.Tests/VisualStudioSolutionWriterTests.cs new file mode 100644 index 00000000..47268a3a --- /dev/null +++ b/test/ScriptCs.Hosting.Tests/VisualStudioSolutionWriterTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Moq; +using ScriptCs.Contracts; +using Should; +using Xunit; + +namespace ScriptCs.Hosting.Tests +{ + public class VisualStudioSolutionWriterTests + { + public class TheWriteSolutionMethod + { + private readonly Mock _solutionMock; + private readonly Mock _fsMock; + private readonly VisualStudioSolutionWriter _writer; + private readonly IList _nestedItems; + private readonly string _launcher; + + public TheWriteSolutionMethod() + { + _writer = new VisualStudioSolutionWriter(); + _solutionMock = new Mock(); + _fsMock = new Mock(); + _fsMock.SetupGet(fs => fs.PackagesFolder).Returns(@"packages"); + _fsMock.SetupGet(fs => fs.HostBin).Returns("bin"); + _fsMock.SetupGet(fs => fs.CurrentDirectory).Returns("root"); + _fsMock.Setup(fs=>fs.EnumerateFilesAndDirectories(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(new[] {Path.Combine("root","file1.csx"), Path.Combine("root", "child1", "file2.csx"), Path.Combine("root", "child1", "child2", "file3.csx")}); + _fsMock.Setup(fs => fs.FileExists(It.IsAny())).Returns(false); + _fsMock.SetupGet(fs => fs.TempPath).Returns("temp"); + _nestedItems = new List(); + _launcher = _writer.WriteSolution(_fsMock.Object, "test.csx", _solutionMock.Object, _nestedItems); + } + + [Fact] + public void ShouldAddTheScriptcsProject() + { + var scriptcsPath = Path.Combine("bin", "scriptcs.exe"); + _solutionMock.Verify(fs=>fs.AddScriptcsProject(scriptcsPath, "root", "test.csx -debug -loglevel info", false, It.IsAny())); + } + + [Fact] + public void ShoulGetDirectoryInfo() + { + _writer.Root.Files.ShouldContain("file1.csx"); + var child = _writer.Root.Directories.Values.First(); + child.Files.ShouldContain("file2.csx"); + child = child.Directories.Values.First(); + child.Files.ShouldContain("file3.csx"); + } + + [Fact] + public void ShouldCallAddDirectoryProjectForChild() + { + var child1 = _writer.Root.Directories.Values.First(); + var child2 = child1.Directories.Values.First(); + _nestedItems.Count(i => i.Project == child1.Guid).ShouldEqual(1); + _nestedItems.Count(i => i.Project == child2.Guid).ShouldEqual(1); + } + + [Fact] + public void ShouldAddGlobal() + { + _solutionMock.Verify(s=>s.AddGlobal(It.IsAny(), _nestedItems)); + } + + [Fact] + public void ShouldWriteTheSolution() + { + _fsMock.Verify(fs=>fs.WriteToFile(It.IsAny(), It.IsAny())); + } + + [Fact] + public void ShouldReturnALauncherInTheTempFolder() + { + _launcher.ShouldNotBeNull(); + _launcher.ShouldStartWith("temp"); + } + } + } +} diff --git a/test/ScriptCs.Hosting.Tests/app.config b/test/ScriptCs.Hosting.Tests/app.config index 601d944d..fe865d69 100644 --- a/test/ScriptCs.Hosting.Tests/app.config +++ b/test/ScriptCs.Hosting.Tests/app.config @@ -3,21 +3,25 @@ - - + + - - + + - - + + - - + + + + + + - \ No newline at end of file + diff --git a/test/ScriptCs.Hosting.Tests/packages.config b/test/ScriptCs.Hosting.Tests/packages.config deleted file mode 100644 index b8044850..00000000 --- a/test/ScriptCs.Hosting.Tests/packages.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/ScriptCs.Tests.Acceptance/CommandLine.cs b/test/ScriptCs.Tests.Acceptance/CommandLine.cs new file mode 100644 index 00000000..b8e78bef --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/CommandLine.cs @@ -0,0 +1,36 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + using Xunit; + + public class CommandLine + { + [Scenario] + public static void UnexpectedOption(Exception exception) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "When I execute scriptcs with an unknown option" + .x(() => exception = Record.Exception(() => ScriptCsExe.Run( + new[] + { + "-unknownoption" + }, + ScenarioDirectory.Create(scenario)))); + + "Then scriptcs errors" + .x(() => exception.ShouldBeType()); + + "And I see an error message regarding the unknown option" + .x(() => + { + Console.WriteLine(exception); + exception.Message.ShouldContain("Usage: scriptcs options"); + }); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Configuration.cs b/test/ScriptCs.Tests.Acceptance/Configuration.cs new file mode 100644 index 00000000..2c0861ed --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Configuration.cs @@ -0,0 +1,52 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + + public static class Configuration + { + [Scenario] + public static void LocalConfiguration(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a hello world script" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(""Hello world!"");")); + + "And a local config file specfying the log level as debug" + .x(() => directory.WriteLine("scriptcs.opts", @"{ logLevel: ""debug"" }")); + + "When I execute the script without the log level option" + .x(() => output = ScriptCsExe.Run("foo.csx", false, directory)); + + "Then I see debug messages" + .x(() => output.ShouldContain("DEBUG:")); + } + + [Scenario] + public static void CustomConfiguration(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a hello world script" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(""Hello world!"");")); + + "And a local config file specfying to run as debug" + .x(() => directory.WriteLine("custom.opts", @"{ logLevel: ""debug"" }")); + + "When I execute the script without the log level option but specifying the custom config" + .x(() => + { + var args = new[] { "--config", "custom.opts", }; + output = ScriptCsExe.Run("foo.csx", false, args, directory); + }); + + "Then I see debug messages" + .x(() => output.ShouldContain("DEBUG:")); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/DirectoryCleaning.cs b/test/ScriptCs.Tests.Acceptance/DirectoryCleaning.cs new file mode 100644 index 00000000..c7cc8e5e --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/DirectoryCleaning.cs @@ -0,0 +1,35 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System.IO; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + + public static class DirectoryCleaning + { + [Scenario] + public static void CleaningADirectory(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a directory" + .x(() => directory = ScenarioDirectory.Create(scenario)); + + "And the directory has an installed package" + .x(() => ScriptCsExe.Install("ScriptCs.Adder.Local", directory)); + + "And the directory has an assembly cache" + .x(() => directory.WriteLine(Path.Combine(directory.Map(ScriptCsExe.DllCacheFolder), "foo.txt"), null)); + + "When I clean the directory" + .x(() => ScriptCsExe.Clean(directory)); + + "Then the packages folder is removed" + .x(() => Directory.Exists(directory.Map(ScriptCsExe.PackagesFolder)).ShouldBeFalse()); + + "And the assembly cache folder is removed" + .x(() => Directory.Exists(directory.Map(ScriptCsExe.DllCacheFolder)).ShouldBeFalse()); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/ImplicitInstallation.cs b/test/ScriptCs.Tests.Acceptance/ImplicitInstallation.cs new file mode 100644 index 00000000..9b2edf33 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/ImplicitInstallation.cs @@ -0,0 +1,53 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System.IO; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + + public static class ImplicitInstallation + { + [Scenario] + public static void Execute(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses ScriptCs.Adder to print the sum of 1234 and 5678" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(Require().Add(1234, 5678));")); + + "And a packages file declaring the ScriptCs.Adder dependency" + .x(() => + { + var nugetConfig = +@" + + + + + + + + + +"; + var packagesConfig = +@" + + + +"; + + directory.WriteLine("scriptcs_nuget.config", nugetConfig); + directory.WriteLine("scriptcs_packages.config", packagesConfig); + }); + + "When execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then I see 6912" + .x(() => output.ShouldContain("6912")); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/LooseScriptExecution.cs b/test/ScriptCs.Tests.Acceptance/LooseScriptExecution.cs new file mode 100644 index 00000000..71af4017 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/LooseScriptExecution.cs @@ -0,0 +1,96 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + using Xunit; + + public static class LooseScriptExecution + { + [Scenario] + [Example(true)] + [Example(false)] + public static void HelloWorld(bool debug, ScenarioDirectory directory, string output, string[] args, string script) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a hello world script" + .x(() => + { + directory = ScenarioDirectory.Create(scenario); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + script = @"Console.WriteLine(""""""Hello World!"""""");"; + } + else + { + script = @"'Console.WriteLine(""Hello World!"");'"; + } + args = new[] {"-e", script}; + }); + + "When I execute the script with debug set to {0}" + .x(() => output = ScriptCsExe.Run(args, debug, directory)); + + "Then I see 'Hello World!'" + .x(() => output.ShouldContain("Hello World!")); + } + + [Scenario] + [Example(true)] + [Example(false)] + public static void ScriptThrowsAnException(bool debug, ScenarioDirectory directory, Exception exception, string[] args, string script) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which throws an exception" + .x(() => + { + directory = ScenarioDirectory.Create(scenario); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + script = @"""throw new Exception(""""""BOOM!"""""");"""; + } + else + { + script = @"'throw new Exception(""BOOM!"");'"; + } + + args = new[] {"-e", script}; + }); + + "When I execute the script with debug set to {0}" + .x(() => exception = Record.Exception(() => ScriptCsExe.Run(args, debug, directory))); + + "Then scriptcs fails" + .x(() => exception.ShouldBeType()); + + "And I see the exception message" + .x(() => + { + exception.Message.ShouldContain("BOOM!"); + }); + } + + [Scenario] + public static void ScriptCanAccessEnv(ScenarioDirectory directory, string output, string[] args, string script) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which access Env" + .x(() => + { + directory = ScenarioDirectory.Create(scenario); + script = "Console.WriteLine(Env)"; + args = new[] {"-e", script}; + }); + "When I execute the script" + .x(() => output = ScriptCsExe.Run(args, directory)); + + "Then the Env object is displayed" + .x(() => output.ShouldContain("ScriptCs.ScriptEnvironment")); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/PackageInstallation.cs b/test/ScriptCs.Tests.Acceptance/PackageInstallation.cs new file mode 100644 index 00000000..f04b3e20 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/PackageInstallation.cs @@ -0,0 +1,42 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System.IO; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + + public static class PackageInstallation + { + [Scenario] + public static void InstallingAPackage(ScenarioDirectory directory) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "When I install ScriptCs.Adder" + .x(() => ScriptCsExe.Install("ScriptCs.Adder.Local", directory = ScenarioDirectory.Create(scenario))); + + "Then the ScriptCs.Adder NuGet package is added to the packages folder" + .x(() => File.Exists( + Path.Combine( + directory.Map(ScriptCsExe.PackagesFolder), + "ScriptCs.Adder.Local.0.1.1/ScriptCs.Adder.Local.0.1.1.nupkg")) + .ShouldBeTrue()); + + "And the ScriptCs.Adder assembly is extracted" + .x(() => + + + File.Exists( + Path.Combine( + directory.Map(ScriptCsExe.PackagesFolder), + "ScriptCs.Adder.Local.0.1.1/lib/net45/ScriptCs.Adder.dll")) + .ShouldBeTrue() + ); + + "And ScriptCs.Adder is added to the packages file" + .x(() => File.ReadAllText(directory.Map(ScriptCsExe.PackagesFile)).ShouldContain( + @"")); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/PackageSaving.cs b/test/ScriptCs.Tests.Acceptance/PackageSaving.cs new file mode 100644 index 00000000..dbc11804 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/PackageSaving.cs @@ -0,0 +1,31 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System.IO; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + + public static class PackageSaving + { + [Scenario] + public static void SavingAPackage(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "When I install ScriptCs.Adder manually" + .x(() => + { + ScriptCsExe.Install("ScriptCs.Adder.Local", directory = ScenarioDirectory.Create(scenario)); + directory.DeleteFile(ScriptCsExe.PackagesFile); + }); + + "And I save packages" + .x(() => ScriptCsExe.Save(directory)); + + "Then ScriptCs.Adder is added to the packages file" + .x(() => File.ReadAllText(directory.Map(ScriptCsExe.PackagesFile)).ShouldContain( + @"")); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Packages.cs b/test/ScriptCs.Tests.Acceptance/Packages.cs new file mode 100644 index 00000000..7527fc5c --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Packages.cs @@ -0,0 +1,53 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Should.Core.Assertions; + using Xbehave; + + public static class Packages + { + [Scenario(Skip = "Failing with a path length exception")] + public static void PackageContainsAFrameworkAssemblyReference(ScenarioDirectory directory, Exception exception) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a simple script" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", "Console.WriteLine();")); + + "When I install a package which contains a framework assembly reference" + .x(() => ScriptCsExe.Install("FrameworkAssemblyReferencer", directory)); + + "And I execute the script" + .x(() => exception = Record.Exception(() => ScriptCsExe.Run("foo.csx", directory))); + + "Then there should be no errors" + .x(() => exception.ShouldBeNull()); + } + + [Scenario] + public static void PackagesWithDuplicateAssemblies(ScenarioDirectory directory, Exception exception) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a simple script" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", "Console.WriteLine();")); + + "When I install a package" + .x(() => ScriptCsExe.Install("Duplicate.A", directory)); + + "And I install another package containing the same assembly" + .x(() => ScriptCsExe.Install("Duplicate.B", directory)); + + "And I execute the script" + .x(() => exception = Record.Exception(() => ScriptCsExe.Run("foo.csx", directory))); + + "Then there should be no errors" + .x(() => exception.ShouldBeNull()); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/ScriptCs.Tests.Acceptance.csproj b/test/ScriptCs.Tests.Acceptance/ScriptCs.Tests.Acceptance.csproj new file mode 100644 index 00000000..765fdba3 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/ScriptCs.Tests.Acceptance.csproj @@ -0,0 +1,43 @@ + + + net461 + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + \ No newline at end of file diff --git a/test/ScriptCs.Tests.Acceptance/ScriptCs.Tests.Acceptance.csproj.DotSettings b/test/ScriptCs.Tests.Acceptance/ScriptCs.Tests.Acceptance.csproj.DotSettings new file mode 100644 index 00000000..73e96563 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/ScriptCs.Tests.Acceptance.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/test/ScriptCs.Tests.Acceptance/ScriptExecution.cs b/test/ScriptCs.Tests.Acceptance/ScriptExecution.cs new file mode 100644 index 00000000..aa3af367 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/ScriptExecution.cs @@ -0,0 +1,177 @@ +using System.IO; + +namespace ScriptCs.Tests.Acceptance +{ + using System; + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + using Xunit; + + public static class ScriptExecution + { + [Scenario] + [Example(true)] + [Example(false)] + public static void HelloWorld(bool debug, ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a hello world script" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(""Hello world!"");")); + + "When I execute the script with debug set to {0}" + .x(() => output = ScriptCsExe.Run("foo.csx", debug, directory)); + + "Then I see 'Hello world!'" + .x(() => output.ShouldContain("Hello world!")); + } + + [Scenario] + [Example(true)] + [Example(false)] + public static void ScriptThrowsAnException(bool debug, ScenarioDirectory directory, Exception exception) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which throws an exception" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"throw new Exception(""BOOM!"");")); + + "When I execute the script with debug set to {0}" + .x(() => exception = Record.Exception(() => ScriptCsExe.Run("foo.csx", debug, directory))); + + "Then scriptcs fails" + .x(() => exception.ShouldBeType()); + + "And I see the exception message" + .x(() => exception.Message.ShouldContain("BOOM!")); + } + + [Scenario] + public static void ScriptCanWorkWithUsingStatic(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which defined a static import" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", "using static System.Console;" + Environment.NewLine + @"WriteLine(""Hello world!"");")); + + "When I execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then I see 'Hello world!'" + .x(() => output.ShouldContain("Hello world!")); + } + + [Scenario] + public static void ScriptingEngineShouldSupportCSharp71(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses C# 7.1 language feature - named tuples" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"var x = 1; var y = 2; var tuple = (x,y); Console.WriteLine(""Sum="" + (tuple.x + tuple.y))")); + + "When I execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then I see the output from the named tuples." + .x(() => output.ShouldContain("Sum=3")); + } + + [Scenario] + public static void ScriptCanAccessEnv(ScenarioDirectory directory, string output ) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which accesses Env" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", "Console.WriteLine(Env)")); + + "When I execute the script" + .x(()=> output = ScriptCsExe.Run("foo.csx", directory)); + + "Then the Env object is displayed" + .x(() => output.ShouldContain("ScriptCs.ScriptEnvironment")); + } + + [Scenario] + public static void ScriptCanUseDynamic(ScenarioDirectory directory, string output ) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses dynamic" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"dynamic obj = new ExpandoObject(); obj.foo = ""bar""; Console.WriteLine(obj.foo); ;")); + + "When I execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then the dynamic value is properly returned " + .x(() => output.ShouldContain("bar")); + + } + + [Scenario] + public static void ScriptAssemblyIsSet(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which accesses Env.ScriptAssembly" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", "Console.WriteLine(Env.ScriptAssembly)")); + + "When I execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then the Assembly is displayed" + .x(() => output.ShouldContain("Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")); + + } + + [Scenario] + public static void ScriptPathIsSet(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which accesses Env.ScriptPath" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", "Console.WriteLine(Env.ScriptPath)")); + + "When I execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then the ScriptPath is displayed" + .x(() => output.ShouldContain("foo.csx")); + } + + [Scenario] + public static void LoadedScriptsIsSet(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which loads another script and accesses Env.LoadedScripts" + .x(() => + { + directory = ScenarioDirectory.Create(scenario) + .WriteLine( + "foo.csx", "#load bar.csx;" + Environment.NewLine + + "Console.WriteLine(Env.LoadedScripts.First());" + ); + directory.WriteLine("bar.csx", ""); + }); + + + "When I execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then the loaded script path is displayed" + .x(() => output.ShouldContain("bar.csx")); + } + + + } +} diff --git a/test/ScriptCs.Tests.Acceptance/ScriptLibraries.cs b/test/ScriptCs.Tests.Acceptance/ScriptLibraries.cs new file mode 100644 index 00000000..1049173e --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/ScriptLibraries.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using Should; +using ScriptCs.Tests.Acceptance.Support; +using Xbehave; + +namespace ScriptCs.Tests.Acceptance +{ + public class ScriptLibraries + { + [Scenario] + public static void UsingAMethodInAScriptLibrary(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses ScriptCs.Calculator to print the sum of 40 and 2" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(new Calculator().Add(40, 2));")); + + "And ScriptCs.Calculator is installed" + .x(() => ScriptCsExe.Install("ScriptCs.Calculator", directory)); + + "When executing the script" + .x(() => + { + var scriptArgs = new[] { "-loglevel", "info" }; + output = ScriptCsExe.Run("foo.csx", false, Enumerable.Empty(), scriptArgs, directory); + }); + + "Then I see 42" + .x(() => output.ShouldContain("42")); + + "Then I see INFO outputted from the required Logger script pack" + .x(() => output.ShouldContain("INFO")); + } + + [Scenario(Skip = "Failing with a path length exception")] + public static void UsingAMethodInAScriptLibraryInTheRepl(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses ScriptCs.Calculator" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(""Type:"" + new Calculator().GetType().Name);" + Environment.NewLine + "Environment.Exit(0);")); + + "And ScriptCs.Calculator is installed" + .x(() => ScriptCsExe.Install("ScriptCs.Calculator", directory)); + + "When executing the script into REPL" + .x(() => output = ScriptCsExe.Run("foo.csx", false, new[] { "-r" }, directory)); + + "Then the ScriptCs.Calculator instance is created" + .x(() => output.ShouldContain("Type:Calculator")); + } + + [Scenario] + public static void LoadingFromANonRootedScript(ScenarioDirectory directory, ScenarioDirectory scriptDirectory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses ScriptCs.Calculator and it is non-rooted" + .x(() => + { + directory = ScenarioDirectory.Create(scenario); + scriptDirectory = ScenarioDirectory.Create(Path.Combine(scenario, "script")) + .WriteLine("foo.csx", @"Console.WriteLine(""Type:"" + new Calculator().GetType().Name);"); + }); + + "And ScriptCs.Calculator is installed" + .x(() => ScriptCsExe.Install("ScriptCs.Calculator", scriptDirectory)); + + "When executing the script" + .x(() => output = ScriptCsExe.Run(Path.Combine("script", "foo.csx"), false, directory)); + + "Then the ScriptCs.Calculator instance is created" + .x(() => output.ShouldContain("Type:Calculator")); + } + + + [Scenario] + public static void UsingALoadedMethodInAScriptLibrary(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses ScriptCs.Calculator to print the product of 7 and 6" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(new Calculator().Multiply(7, 6));")); + + "And ScriptCs.Calculator is installed" + .x(() => ScriptCsExe.Install("ScriptCs.Calculator", directory)); + + "When executing the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then I see 42" + .x(() => output.ShouldContain("42")); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/ScriptPacks.cs b/test/ScriptCs.Tests.Acceptance/ScriptPacks.cs new file mode 100644 index 00000000..d5f16da3 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/ScriptPacks.cs @@ -0,0 +1,56 @@ +namespace ScriptCs.Tests.Acceptance +{ + using System.Reflection; + using ScriptCs.Tests.Acceptance.Support; + using Should; + using Xbehave; + using System; + using Should.Core.Assertions; + using ScriptCs.Exceptions; + + public static class ScriptPacks + { + [Scenario] + public static void UsingAScriptPack(ScenarioDirectory directory, string output) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses ScriptCs.Adder to print the sum of 1234 and 5678" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"Console.WriteLine(Require().Add(1234, 5678));")); + + "And ScriptCs.Adder is installed" + .x(() => ScriptCsExe.Install("ScriptCs.Adder.Local", directory)); + + "When execute the script" + .x(() => output = ScriptCsExe.Run("foo.csx", directory)); + + "Then I see 6912" + .x(() => output.ShouldContain("6912")); + } + + [Scenario] + public static void UsingAnUnavailableScriptPack(ScenarioDirectory directory, string output, Exception exception) + { + var scenario = MethodBase.GetCurrentMethod().GetFullName(); + + "Given a script which uses a non-existing script pack 'Foo'" + .x(() => directory = ScenarioDirectory.Create(scenario) + .WriteLine("foo.csx", @"var fooContext = Require(); + class Foo : ScriptCs.Contracts.IScriptPackContext{} + Console.WriteLine(""hi"");")); + + "When I execute the script" + .x(() => exception = Record.Exception(() => ScriptCsExe.Run("foo.csx", directory))); + + "Then scriptcs fails" + .x(() => exception.ShouldBeType()); + + "With a script pack exception" + .x(() => + { + exception.Message.ShouldContain("Tried to resolve a script pack 'Submission#0+Foo', but such script pack is not available in the current execution context."); + }); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Support/FileSystem.cs b/test/ScriptCs.Tests.Acceptance/Support/FileSystem.cs new file mode 100644 index 00000000..d21f4580 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Support/FileSystem.cs @@ -0,0 +1,115 @@ +using System.Diagnostics; + +namespace ScriptCs.Tests.Acceptance.Support +{ + using System; + using System.Globalization; + using System.IO; + using Should; + + // NOTE (adamralph): difficult to believe the retry stuff is required, but it is. System.IO and the filesystem race. + public static class FileSystem + { + public static void EnsureDirectoryCreated(string path) + { + Debug.WriteLine($"Current Directory [{Environment.CurrentDirectory.Length}]: {Environment.CurrentDirectory}"); + + if (Directory.Exists(path)) + { + return; + } + + var timeout = 0.05d; + + var createTimeout = DateTime.Now.AddSeconds(timeout); + while (true) + { + try + { + Directory.CreateDirectory(path); + + var existsTimeout = DateTime.Now.AddSeconds(timeout); + while (true) + { + if (Directory.Exists(path)) + { + break; + } + + if (DateTime.Now < existsTimeout) + { + continue; + } + + throw new IOException( + string.Format(CultureInfo.InvariantCulture, "Failed to create directory '{0}'", path)); + } + } + catch (Exception) + { + if (DateTime.Now < createTimeout) + { + continue; + } + + throw; + } + + break; + } + } + + public static void EnsureDirectoryDeleted(string path) + { + if (!Directory.Exists(path)) + { + return; + } + + var timeout = 0.05d; + + var deleteTimeout = DateTime.Now.AddSeconds(timeout); + while (true) + { + try + { + Directory.Delete(path, true); + + var goneTimeout = DateTime.Now.AddSeconds(timeout); + while (true) + { + if (!Directory.Exists(path)) + { + break; + } + + if (DateTime.Now < goneTimeout) + { + continue; + } + + throw new IOException( + string.Format(CultureInfo.InvariantCulture, "Failed to delete directory '{0}'", path)); + } + } + catch (Exception) + { + if (DateTime.Now < deleteTimeout) + { + continue; + } + + throw; + } + + break; + } + } + + public static void EnsureFileDeleted(string fileName) + { + File.Delete(fileName); + File.Exists(fileName).ShouldBeFalse(fileName + " should be deleted"); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/Duplicate.A.1.0.0.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/Duplicate.A.1.0.0.nupkg new file mode 100644 index 00000000..f29cc9b3 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/Duplicate.A.1.0.0.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/Duplicate.B.1.0.0.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/Duplicate.B.1.0.0.nupkg new file mode 100644 index 00000000..f80b14d4 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/Duplicate.B.1.0.0.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/FrameworkAssemblyReferencer.1.0.0.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/FrameworkAssemblyReferencer.1.0.0.nupkg new file mode 100644 index 00000000..25061b67 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/FrameworkAssemblyReferencer.1.0.0.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Adder.Local.0.1.1.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Adder.Local.0.1.1.nupkg new file mode 100644 index 00000000..ea727f28 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Adder.Local.0.1.1.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Calculator.0.1.0.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Calculator.0.1.0.nupkg new file mode 100644 index 00000000..f3e45144 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Calculator.0.1.0.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Contracts.0.9.0.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Contracts.0.9.0.nupkg new file mode 100644 index 00000000..17d6a053 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Contracts.0.9.0.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Logger.ScriptPack.0.1.0.nupkg b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Logger.ScriptPack.0.1.0.nupkg new file mode 100644 index 00000000..cc657944 Binary files /dev/null and b/test/ScriptCs.Tests.Acceptance/Support/Gallery/ScriptCs.Logger.ScriptPack.0.1.0.nupkg differ diff --git a/test/ScriptCs.Tests.Acceptance/Support/MethodBaseExtensions.cs b/test/ScriptCs.Tests.Acceptance/Support/MethodBaseExtensions.cs new file mode 100644 index 00000000..0acc3ce1 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Support/MethodBaseExtensions.cs @@ -0,0 +1,43 @@ +using System.Security.Cryptography; +using System.Text; + +namespace ScriptCs.Tests.Acceptance.Support +{ + using System.Reflection; + using ScriptCs; + + public static class MethodBaseExtensions + { + public static string GetFullName(this MethodBase method) + { + Guard.AgainstNullArgument(nameof(method), method); + + return method.DeclaringType == null + ? method.Name + : string.Concat(GenerateSimpleHash(method.DeclaringType.FullName), ".", method.Name); + } + + /// + /// Windows cannot handle long path, so create a short path + /// + /// + /// + private static string GenerateSimpleHash(string name) + { + // use a hash instead of random characters + // so we have some consistency in debugging folders + using (var md5Hash = MD5.Create()) + { + var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(name)); + var hash = new StringBuilder(10); + + for (var i = 0; i < 5; i++) + { + hash.Append(data[i].ToString("x2")); + } + + return hash.ToString(); + } + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Support/ProcessStartInfoExtensions.cs b/test/ScriptCs.Tests.Acceptance/Support/ProcessStartInfoExtensions.cs new file mode 100644 index 00000000..037a2e8f --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Support/ProcessStartInfoExtensions.cs @@ -0,0 +1,49 @@ +namespace ScriptCs.Tests.Acceptance.Support +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Text; + + public static class ProcessStartInfoExtensions + { + public static Tuple Run(this ProcessStartInfo info, string outputFile) + { + var output = new StringBuilder(); + int exitCode; + using (var process = new Process()) + { + process.StartInfo = info; + process.OutputDataReceived += (sender, e) => output.AppendLine(e.Data); + process.ErrorDataReceived += (sender, e) => output.AppendLine(e.Data); + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + if (!process.WaitForExit(30000)) + { + try + { + process.Kill(); + } + catch (Exception ex) + { + throw new TimeoutException( + "The process took too longer than 30 seconds to exit and killing the process failed.", ex); + } + + throw new TimeoutException("The process took longer than 30 seconds to exit."); + } + + using (var writer = new StreamWriter(outputFile, true)) + { + writer.WriteLine(output.ToString()); + writer.Flush(); + } + + exitCode = process.ExitCode; + } + + return Tuple.Create(exitCode, output.ToString()); + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Support/ScenarioDirectory.cs b/test/ScriptCs.Tests.Acceptance/Support/ScenarioDirectory.cs new file mode 100644 index 00000000..d102e21e --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Support/ScenarioDirectory.cs @@ -0,0 +1,60 @@ +using System; + +namespace ScriptCs.Tests.Acceptance.Support +{ + using System.Diagnostics; + using System.IO; + + public sealed class ScenarioDirectory + { + private static readonly string rootDirectory = "scenarios"; + + private readonly string _name; + + public static ScenarioDirectory Create(string scenario) + { + var name = Path.Combine(rootDirectory, scenario); + if (name == null) throw new Exception("Invalid directory"); + Debug.WriteLine($"Scenarios Dir [{name.Length}]: {name}"); + FileSystem.EnsureDirectoryDeleted(name); + FileSystem.EnsureDirectoryCreated(name); + return new ScenarioDirectory(name); + } + + private ScenarioDirectory(string name) + { + _name = name; + } + + public string Name + { + get { return _name; } + } + + public ScenarioDirectory WriteLine(string fileName, string text) + { + fileName = Map(fileName); + FileSystem.EnsureDirectoryCreated(Path.GetDirectoryName(fileName)); + using (var writer = new StreamWriter(fileName, true)) + { + writer.WriteLine(text); + writer.Flush(); + writer.Close(); + } + + return this; + } + + public void DeleteFile(string fileName) + { + FileSystem.EnsureFileDeleted(Map(fileName)); + } + + public string Map(string path) + { + var mapPath = Path.Combine(_name, path); + Debug.WriteLine($"Map [{mapPath.Length}]: {mapPath}"); + return mapPath; + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Support/ScriptCsException.cs b/test/ScriptCs.Tests.Acceptance/Support/ScriptCsException.cs new file mode 100644 index 00000000..2a8f62e4 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Support/ScriptCsException.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.Serialization; + +namespace ScriptCs.Tests.Acceptance.Support +{ + [Serializable] + public class ScriptCsException : Exception + { + public ScriptCsException() + { + } + + public ScriptCsException(string message) + : base(message) + { + } + + public ScriptCsException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ScriptCsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/Support/ScriptCsExe.cs b/test/ScriptCs.Tests.Acceptance/Support/ScriptCsExe.cs new file mode 100644 index 00000000..d2013d71 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/Support/ScriptCsExe.cs @@ -0,0 +1,169 @@ +namespace ScriptCs.Tests.Acceptance.Support +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + + public static class ScriptCsExe + { + private static readonly bool isMono = Type.GetType("Mono.Runtime") != null; + + public static string BinFolder + { + get { return "scriptcs_bin"; } + } + + public static string DllCacheFolder + { + get { return ".scriptcs_cache"; } + } + + public static string PackagesFile + { + get { return "scriptcs_packages.config"; } + } + + public static string PackagesFolder + { + get { return "scriptcs_packages"; } + } + + public static string NugetFile + { + get { return "scriptcs_nuget.config"; } + } + + public static string Run(IEnumerable args, ScenarioDirectory directory) + { + return Run(null, true, args, Enumerable.Empty(), directory); + } + + public static string Run(IEnumerable args, bool debug, ScenarioDirectory directory) + { + return Run(null, debug, args, Enumerable.Empty(), directory); + } + + public static string Run(string scriptName, ScenarioDirectory directory) + { + return Run(scriptName, true, Enumerable.Empty(), Enumerable.Empty(), directory); + } + + public static string Run(string scriptName, bool debug, ScenarioDirectory directory) + { + return Run(scriptName, debug, Enumerable.Empty(), Enumerable.Empty(), directory); + } + + public static string Run(string scriptName, bool debug, IEnumerable args, ScenarioDirectory directory) + { + return Run(scriptName, debug, args, Enumerable.Empty(), directory); + } + + public static string Run( + string scriptName, + bool debug, + IEnumerable args, + IEnumerable scriptArgs, + ScenarioDirectory directory) + { + var debugArgs = + debug && + !args.Select(arg => arg.Trim().ToUpperInvariant()).Contains("-DEBUG") && + !args.Select(arg => arg.Trim().ToUpperInvariant()).Contains("-D") + ? new[] { "--debug" } + : new string[0]; + + return Execute( + (scriptName == null ? Enumerable.Empty() : new[] { scriptName }).Concat(debugArgs).Concat(args), + scriptArgs, + directory); + } + + public static string Install(string package, ScenarioDirectory directory) + { + using (var writer = new StreamWriter(directory.Map(NugetFile), false)) + { + writer.Write( +@" + + + + + + + + +" + ); + + writer.Flush(); + } + + return Execute(new[] { "install", package }, Enumerable.Empty(), directory); + } + + public static string Save(ScenarioDirectory directory) + { + return Execute(new[] { "install", "--save" }, Enumerable.Empty(), directory); + } + + public static string Clean(ScenarioDirectory directory) + { + return Execute(new[] { "install", "--clean" }, Enumerable.Empty(), directory); + } + + private static string Execute( + IEnumerable args, IEnumerable scriptArgs, ScenarioDirectory directory) + { + var commandArgs = new List(args); + var scriptArgsArray = scriptArgs.ToArray(); + if (scriptArgsArray.Any()) + { + commandArgs.Add("--"); + commandArgs.AddRange(scriptArgsArray); + } + +#if DEBUG + var config = "Debug"; +#else + var config = "Release"; +#endif + + var exe = Path.GetFullPath( + Path.Combine("..", "..", "..", "..", "..", "src", "ScriptCs", "bin", config, "net461", "scriptcs.exe")); + + var info = new ProcessStartInfo + { + FileName = isMono + ? "mono" + : exe, + Arguments = isMono + ? string.Concat(exe, " ", string.Join(" ", commandArgs)) + : string.Join(" ", commandArgs), + WorkingDirectory = directory.Name, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardErrorEncoding = Encoding.UTF8, + StandardOutputEncoding = Encoding.UTF8 + }; + var result = info.Run(Path.GetFileName(directory.Name) + ".log"); + if (result.Item1 != 0) + { + var message = string.Format( + CultureInfo.InvariantCulture, + "scriptcs.exe exited with code {0}. The output was: {1}", + result.Item1.ToString(CultureInfo.InvariantCulture), + result.Item2); + + throw new ScriptCsException(message); + } + + return result.Item2; + } + } +} diff --git a/test/ScriptCs.Tests.Acceptance/app.config b/test/ScriptCs.Tests.Acceptance/app.config new file mode 100644 index 00000000..74979dd4 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/app.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ScriptCs.Tests.Acceptance/xunit.runner.json b/test/ScriptCs.Tests.Acceptance/xunit.runner.json new file mode 100644 index 00000000..edb23212 --- /dev/null +++ b/test/ScriptCs.Tests.Acceptance/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "parallelizeTestCollections": false +} \ No newline at end of file diff --git a/test/ScriptCs.Tests/ArgumentHandlerTests.cs b/test/ScriptCs.Tests/ArgumentHandlerTests.cs deleted file mode 100644 index 75585e16..00000000 --- a/test/ScriptCs.Tests/ArgumentHandlerTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Moq; -using ScriptCs.Argument; -using ScriptCs.Contracts; -using Should; -using Xunit; - -namespace ScriptCs.Tests -{ - public class ArgumentHandlerTests - { - public class ParseMethod - { - private static IArgumentHandler Setup(string fileContent, string fileName = "scriptcs.opts", bool fileExists = true) - { - const string currentDirectory = "C:\\test\\folder"; - - string filePath = currentDirectory + '\\' + fileName; - - var fs = new Mock(); - fs.SetupGet(x => x.CurrentDirectory).Returns(currentDirectory); - fs.Setup(x => x.FileExists(filePath)).Returns(fileExists); - fs.Setup(x => x.ReadFile(filePath)).Returns(fileContent); - var console = new ScriptConsole(); - - return new ArgumentHandler(new ArgumentParser(console), new ConfigFileParser(console), fs.Object); - } - - [Fact] - public void ShouldHandleConfigFileAndCommandLineArguments() - { - const string file = "{\"Install\": \"install test value\" }"; - string[] args = { "server.csx", "-log", "error", "--", "-port", "8080" }; - - var argumentHandler = Setup(file); - var result = argumentHandler.Parse(args); - - result.ShouldNotBeNull(); - result.Arguments.ShouldEqual(args); - result.CommandArguments.ScriptName.ShouldEqual("server.csx"); - result.CommandArguments.LogLevel.ShouldEqual(LogLevel.Error); - result.CommandArguments.Install.ShouldEqual("install test value"); - result.ScriptArguments.ShouldEqual(new string[] { "-port", "8080" }); - } - - [Fact] - public void ShouldHandleCommandLineArgumentsOverConfigFile() - { - const string file = "{\"Install\": \"config file arg\", \"debug\": \"true\" }"; - string[] args = { "server.csx", "-Install", "command line arg", "-cache", "--", "-port", "8080" }; - - var argumentHandler = Setup(file); - var result = argumentHandler.Parse(args); - - result.ShouldNotBeNull(); - result.Arguments.ShouldEqual(args); - result.CommandArguments.ScriptName.ShouldEqual("server.csx"); - result.CommandArguments.Install.ShouldEqual("command line arg"); - result.CommandArguments.Cache.ShouldEqual(true); - result.ScriptArguments.ShouldEqual(new string[] { "-port", "8080" }); - } - - [Fact] - public void ShouldHandleCommandLineArgumentsOverConfigFileWithPropertyName() - { - const string file = "{\"LogLevel\": \"info\", }"; - string[] args = { "server.csx", "-log", "error", "--", "-port", "8080" }; - - var argumentHandler = Setup(file); - var result = argumentHandler.Parse(args); - - result.ShouldNotBeNull(); - result.Arguments.ShouldEqual(args); - result.CommandArguments.ScriptName.ShouldEqual("server.csx"); - result.CommandArguments.LogLevel.ShouldEqual(LogLevel.Error); - result.ScriptArguments.ShouldEqual(new string[] { "-port", "8080" }); - } - - [Fact] - public void ShouldHandleInvalidCommandLineArguments() - { - string[] args = { "-version", "-foo", "-bar" }; - - var argumentHandler = Setup(null, "test.txt", false); - var result = argumentHandler.Parse(args); - - result.CommandArguments.ShouldBeNull(); - result.Arguments.Length.ShouldEqual(3); - } - - [Fact] - public void ShouldHandleOnlyCommandLineArguments() - { - string[] args = { "server.csx", "--", "-port", "8080" }; - - var argumentHandler = Setup(null, "test.txt", false); - var result = argumentHandler.Parse(args); - - result.ShouldNotBeNull(); - result.Arguments.ShouldEqual(args); - result.CommandArguments.ScriptName.ShouldEqual("server.csx"); - result.ScriptArguments.ShouldEqual(new string[] { "-port", "8080" }); - } - - [Fact] - public void ShouldHandleOnlyConfigFile() - { - const string file = "{\"log\": \"error\", \"script\": \"server.csx\" }"; - - var argumentHandler = Setup(file); - var result = argumentHandler.Parse(new string[0]); - - result.ShouldNotBeNull(); - result.CommandArguments.ScriptName.ShouldEqual("server.csx"); - result.CommandArguments.LogLevel.ShouldEqual(LogLevel.Error); - result.ScriptArguments.ShouldEqual(new string[0]); - } - - [Fact] - public void ShouldHandleCustomConfigFile() - { - const string fileName = "text.txt"; - const string file = "{\"Install\": \"install test value\" }"; - string[] args = { "server.csx", "-log", "error", "-config", fileName, "--", "-port", "8080" }; - - var argumentHandler = Setup(file, fileName); - var result = argumentHandler.Parse(args); - - result.ShouldNotBeNull(); - result.Arguments.ShouldEqual(args); - result.CommandArguments.ScriptName.ShouldEqual("server.csx"); - result.CommandArguments.LogLevel.ShouldEqual(LogLevel.Error); - result.ScriptArguments.ShouldEqual(new string[] { "-port", "8080" }); - result.CommandArguments.Install.ShouldEqual("install test value"); - } - - [Fact] - public void ShouldHandleHelp() - { - string[] args = { "-help" }; - - var argumentHandler = Setup(null); - var result = argumentHandler.Parse(args); - - result.ShouldNotBeNull(); - result.Arguments.ShouldEqual(args); - result.CommandArguments.ScriptName.ShouldBeNull(); - result.CommandArguments.Help.ShouldBeTrue(); - } - } - } -} \ No newline at end of file diff --git a/test/ScriptCs.Tests/ArgumentParserTests.cs b/test/ScriptCs.Tests/ArgumentParserTests.cs deleted file mode 100644 index f6fdfb43..00000000 --- a/test/ScriptCs.Tests/ArgumentParserTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using ScriptCs.Argument; -using ScriptCs.Contracts; -using Should; -using Xunit; - -namespace ScriptCs.Tests -{ - public class ArgumentParserTests - { - public class ParseMethod - { - [Fact] - public void ShouldHandleCommandLineArguments() - { - string[] args = { "server.csx", "-log", "error" }; - - var parser = new ArgumentParser(new ScriptConsole()); - var result = parser.Parse(args); - - result.ShouldNotBeNull(); - result.ScriptName.ShouldEqual("server.csx"); - result.LogLevel.ShouldEqual(LogLevel.Error); - } - - [Fact] - public void ShouldHandleEmptyAttray() - { - var parser = new ArgumentParser(new ScriptConsole()); - var result = parser.Parse(new string[0]); - - result.ShouldNotBeNull(); - result.Repl.ShouldBeTrue(); - result.LogLevel.ShouldEqual(LogLevel.Info); - result.Config.ShouldEqual("scriptcs.opts"); - } - - [Fact] - public void ShouldHandleNull() - { - var parser = new ArgumentParser(new ScriptConsole()); - var result = parser.Parse(null); - - result.ShouldNotBeNull(); - result.Repl.ShouldBeTrue(); - result.LogLevel.ShouldEqual(LogLevel.Info); - result.Config.ShouldEqual("scriptcs.opts"); - } - - [Fact] - public void ShouldSupportHelp() - { - string[] args = { "-help" }; - - var parser = new ArgumentParser(new ScriptConsole()); - var result = parser.Parse(args); - - result.ShouldNotBeNull(); - result.ScriptName.ShouldBeNull(); - result.Help.ShouldBeTrue(); - result.LogLevel.ShouldEqual(LogLevel.Info); - } - - [Fact] - public void ShouldGoIntoReplIfOnlyLogLevelIsSet() - { - string[] args = { "-loglevel", "debug" }; - - var parser = new ArgumentParser(new ScriptConsole()); - var result = parser.Parse(args); - - result.Repl.ShouldBeTrue(); - result.LogLevel.ShouldEqual(LogLevel.Debug); - } - - [Fact] - public void ShouldGoIntoReplIfOnlyLogIsSet() - { - string[] args = { "-log", "debug" }; - - var parser = new ArgumentParser(new ScriptConsole()); - var result = parser.Parse(args); - - result.Repl.ShouldBeTrue(); - result.LogLevel.ShouldEqual(LogLevel.Debug); - } - } - } -} \ No newline at end of file diff --git a/test/ScriptCs.Tests/CleanCommandTests.cs b/test/ScriptCs.Tests/CleanCommandTests.cs index 6e4c01de..d49cfffc 100644 --- a/test/ScriptCs.Tests/CleanCommandTests.cs +++ b/test/ScriptCs.Tests/CleanCommandTests.cs @@ -1,32 +1,41 @@ using Moq; - -using Ploeh.AutoFixture.Xunit; - +using AutoFixture.Xunit2; using ScriptCs.Command; using ScriptCs.Contracts; -using Xunit.Extensions; +using ScriptCs.Hosting; +using Xunit; namespace ScriptCs.Tests -{ +{ public class CleanCommandTests { public class ExecuteMethod { - [Theory, ScriptCsAutoData] - public void ShouldDeletePackagesFolder([Frozen] Mock fileSystem, CommandFactory factory) + [ScriptCsAutoData("scriptcs_packages")] + [ScriptCsAutoData(".scriptcs_cache")] + [Theory] + public void ShouldDeletePackagesFolder(string folder, + [Frozen] Mock fileSystem, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { // Arrange - var args = new ScriptCsArgs { Clean = true }; + var args = new Config { Clean = true }; + + fileSystem.Setup(i => i.DirectoryExists(It.Is(x => x.Contains(folder)))).Returns(true); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); - fileSystem.Setup(i => i.DirectoryExists(It.Is(x => x.Contains(Constants.PackagesFolder)))).Returns(true); - fileSystem.Setup(i => i.GetWorkingDirectory(It.IsAny())).Returns("c:\\"); + var factory = new CommandFactory(servicesBuilder.Object); // Act factory.CreateCommand(args, new string[0]).Execute(); // Assert - fileSystem.Verify(i => i.DirectoryExists(It.Is(x => x.Contains(Constants.PackagesFolder))), Times.Once()); - fileSystem.Verify(i => i.DeleteDirectory(It.Is(x => x.Contains(Constants.PackagesFolder))), Times.Once()); + fileSystem.Verify(i => i.DirectoryExists(It.Is(x => x.Contains(folder))), Times.Once()); + fileSystem.Verify(i => i.DeleteDirectory(It.Is(x => x.Contains(folder))), Times.Once()); } } } diff --git a/test/ScriptCs.Tests/CommandFactoryTests.cs b/test/ScriptCs.Tests/CommandFactoryTests.cs index 56ca3054..31eaede7 100644 --- a/test/ScriptCs.Tests/CommandFactoryTests.cs +++ b/test/ScriptCs.Tests/CommandFactoryTests.cs @@ -1,10 +1,9 @@ using Moq; - -using Ploeh.AutoFixture; -using Ploeh.AutoFixture.AutoMoq; - +using AutoFixture; +using AutoFixture.AutoMoq; using ScriptCs.Command; using ScriptCs.Contracts; +using ScriptCs.Hosting; using Should; using Xunit; @@ -14,80 +13,87 @@ public class CommandFactoryTests { public class CreateCommandMethod { - private static ScriptServices CreateRoot(bool packagesFileExists = true, bool packagesFolderExists = true) + private static IScriptServicesBuilder CreateBuilder(bool packagesFileExists = true, bool packagesFolderExists = true) { const string CurrentDirectory = "C:\\"; const string PackagesFile = "C:\\packages.config"; const string PackagesFolder = "C:\\packages"; - + var fixture = new Fixture().Customize(new AutoMoqCustomization()); - - var fs = new Mock(); - fs.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); - fs.Setup(x => x.FileExists(PackagesFile)).Returns(packagesFileExists); - fs.Setup(x => x.DirectoryExists(PackagesFolder)).Returns(packagesFolderExists); - - fixture.Register(() => fs.Object); - - return fixture.Create(); + var fileSystem = fixture.Freeze>(); + fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); + fileSystem.SetupGet(x => x.PackagesFile).Returns("packages.config"); + fileSystem.SetupGet(x => x.PackagesFolder).Returns("packages"); + fileSystem.SetupGet(x => x.DllCacheFolder).Returns(".cache"); + fileSystem.Setup(x => x.FileExists(PackagesFile)).Returns(packagesFileExists); + fileSystem.Setup(x => x.DirectoryExists(PackagesFolder)).Returns(packagesFolderExists); + + var builder = fixture.Freeze>(); + var services = fixture.Create(); + builder.Setup(b => b.Build()).Returns(services); + + var initServices = fixture.Freeze>(); + initServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + builder.SetupGet(b => b.InitializationServices).Returns(initServices.Object); + return fixture.Create(); } [Fact] - public void ShouldInstallAndRestoreWhenInstallFlagIsOn() + public void ShouldInstallAndSaveWhenInstallFlagIsOn() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = string.Empty, + PackageName = string.Empty, ScriptName = null }; // Act - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert - result.ShouldImplement(); + var compositeCommand = result as ICompositeCommand; + compositeCommand.ShouldNotBeNull(); + + compositeCommand.Commands.Count.ShouldEqual(2); + compositeCommand.Commands[0].ShouldImplement(); + compositeCommand.Commands[1].ShouldImplement(); } [Fact] - public void ShouldInstallAndSaveWhenInstallFlagIsOnAndNoPackagesFileExists() + public void ShouldExecuteLooseScriptWhenExecIsPassed() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = string.Empty, - ScriptName = null + PackageName = null, + Eval = "foo" }; // Act - var factory = new CommandFactory(CreateRoot(packagesFileExists: false)); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert - var compositeCommand = result as ICompositeCommand; - compositeCommand.ShouldNotBeNull(); - - compositeCommand.Commands.Count.ShouldEqual(2); - compositeCommand.Commands[0].ShouldImplement(); - compositeCommand.Commands[1].ShouldImplement(); - } + result.ShouldImplement(); + } [Fact] public void ShouldExecuteWhenScriptNameIsPassed() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = null, + PackageName = null, ScriptName = "test.csx" }; // Act - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert @@ -98,16 +104,15 @@ public void ShouldExecuteWhenScriptNameIsPassed() public void ShouldInstallAndExecuteWhenScriptNameIsPassedAndPackagesFolderDoesNotExist() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = null, + PackageName = null, ScriptName = "test.csx" }; // Act - var root = CreateRoot(packagesFileExists: true, packagesFolderExists: false); - var factory = new CommandFactory(root); + var factory = new CommandFactory(CreateBuilder(true, false)); var result = factory.CreateCommand(args, new string[0]); // Assert @@ -116,22 +121,22 @@ public void ShouldInstallAndExecuteWhenScriptNameIsPassedAndPackagesFolderDoesNo compositeCommand.Commands.Count.ShouldEqual(2); compositeCommand.Commands[0].ShouldImplement(); - compositeCommand.Commands[1].ShouldImplement(); + compositeCommand.Commands[1].ShouldImplement>(); } [Fact] public void ShouldExecuteWhenBothNameAndInstallArePassed() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = string.Empty, + PackageName = string.Empty, ScriptName = "test.csx" }; // Act - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert @@ -142,10 +147,10 @@ public void ShouldExecuteWhenBothNameAndInstallArePassed() public void ShouldSaveAndCleanWhenCleanFlagIsPassed() { // Arrange - var args = new ScriptCsArgs { Clean = true, ScriptName = null }; + var args = new Config { Clean = true, ScriptName = null }; // Act - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert @@ -161,10 +166,10 @@ public void ShouldSaveAndCleanWhenCleanFlagIsPassed() public void ShouldSaveWhenSaveFlagIsPassed() { // Arrange - var args = new ScriptCsArgs { Save = true, ScriptName = null }; + var args = new Config { Save = true, ScriptName = null }; // Act - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert @@ -173,52 +178,38 @@ public void ShouldSaveWhenSaveFlagIsPassed() } [Fact] - public void ShouldReturnInvalidWhenNoNameOrInstallSet() + public void ShouldReturnReplWhenNoNameOrInstallSet() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = null, + PackageName = null, ScriptName = null }; // Act - var factory = new CommandFactory(CreateRoot()); - var result = factory.CreateCommand(args, new string[0]); - - // Assert - result.ShouldImplement(); - } - - [Fact] - public void ShouldReturnHelpCommandWhenHelpIsPassed() - { - // Arrange - var args = new ScriptCsArgs { Help = true }; - - // Act - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, new string[0]); // Assert - result.ShouldImplement(); + result.ShouldImplement(); } [Fact] public void ShouldPassScriptArgsToExecuteCommandConstructor() { // Arrange - var args = new ScriptCsArgs + var args = new Config { AllowPreRelease = false, - Install = null, + PackageName = null, ScriptName = "test.csx" }; // Act var scriptArgs = new string[0]; - var factory = new CommandFactory(CreateRoot()); + var factory = new CommandFactory(CreateBuilder()); var result = factory.CreateCommand(args, scriptArgs) as IScriptCommand; // Assert @@ -226,4 +217,4 @@ public void ShouldPassScriptArgsToExecuteCommandConstructor() } } } -} +} \ No newline at end of file diff --git a/test/ScriptCs.Tests/ConfigFileParserTests.cs b/test/ScriptCs.Tests/ConfigFileParserTests.cs deleted file mode 100644 index 619e76cd..00000000 --- a/test/ScriptCs.Tests/ConfigFileParserTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -using ScriptCs.Argument; -using ScriptCs.Contracts; -using Xunit; -using Should; - -namespace ScriptCs.Tests -{ - public class ConfigFileParserTests - { - public class ParseMethod - { - [Fact] - public void ShouldHandleConfigFile() - { - const string file = "{\"Install\": \"install test value\", \"script\": \"server.csx\" }"; - - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(file); - - result.ShouldNotBeNull(); - result.ScriptName.ShouldEqual("server.csx"); - result.Install.ShouldEqual("install test value"); - } - - [Fact] - public void ShouldHandleNull() - { - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(null); - - result.ShouldBeNull(); - } - - [Fact] - public void ShouldHandleEmptyString() - { - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(""); - - result.ShouldBeNull(); - } - - [Fact] - public void ShouldHanldeArgumentTypeConversionBool() - { - const string file = "{\"Install\": \"install test value\", \"script\": \"server.csx\", \"cache\": \"true\" }"; - - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(file); - - result.ShouldNotBeNull(); - result.ScriptName.ShouldEqual("server.csx"); - result.Install.ShouldEqual("install test value"); - result.Cache.ShouldEqual(true); - } - - [Fact] - public void ShouldHanldeArgumentTypeConversionEnum() - { - const string file = "{\"Install\": \"install test value\", \"script\": \"server.csx\", \"cache\": \"true\", \"log\": \"error\" }"; - - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(file); - - result.ShouldNotBeNull(); - result.ScriptName.ShouldEqual("server.csx"); - result.Install.ShouldEqual("install test value"); - result.Cache.ShouldEqual(true); - result.LogLevel.ShouldEqual(LogLevel.Error); - } - - [Fact] - public void ShouldHandleConfigArgumentsCaseInsensitive() - { - const string file = "{\"Install\": \"install test value\", \"script\": \"server.csx\", \"cache\": \"tRUe\", \"logLEVEL\": \"TRaCE\" }"; - - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(file); - - result.ShouldNotBeNull(); - result.ScriptName.ShouldEqual("server.csx"); - result.Install.ShouldEqual("install test value"); - result.Cache.ShouldEqual(true); - result.LogLevel.ShouldEqual(LogLevel.Trace); - } - - [Fact] - public void ShouldHandleConfigMalformedConfig() - { - const string file = "{\"Install\": \"install "; - - var parser = new ConfigFileParser(new ScriptConsole()); - var result = parser.Parse(file); - - result.ShouldBeNull(); - } - } - } -} \ No newline at end of file diff --git a/test/ScriptCs.Tests/ConfigTests.cs b/test/ScriptCs.Tests/ConfigTests.cs new file mode 100644 index 00000000..040084d0 --- /dev/null +++ b/test/ScriptCs.Tests/ConfigTests.cs @@ -0,0 +1,54 @@ +using Should; +using Xunit.Extensions; + +namespace ScriptCs.Tests +{ + using ScriptCs.Contracts; + using Xunit; + + public class ConfigTests + { + public class TheApplyMethod + { + [Theory] + [InlineData(true, null, LogLevel.Error, LogLevel.Debug)] + [InlineData(false, null, LogLevel.Error, LogLevel.Error)] + [InlineData(true, LogLevel.Error, LogLevel.Trace, LogLevel.Error)] + [InlineData(true, null, LogLevel.Trace, LogLevel.Trace)] + public void CalculatesTheLogLevel( + bool debug, LogLevel? logLevel, LogLevel currentLogLevel, LogLevel expectedLogLevel) + { + // arrange + var mask = new ConfigMask { Debug = debug, LogLevel = logLevel }; + var config = new Config { LogLevel = currentLogLevel }; + + // act + config = config.Apply(mask); + + // assert + config.LogLevel.ShouldEqual(expectedLogLevel); + } + + [Theory] + [InlineData(null, null)] + [InlineData(".csx", ".csx")] + [InlineData(".fsx", ".fsx")] + [InlineData("a", "a.csx")] // :eyes: here it is! + [InlineData("a.", "a.")] + [InlineData("a.csx", "a.csx")] + [InlineData("a.fsx", "a.fsx")] + public void AddsTheDefaultExtension(string scriptName, string expectedScriptName) + { + // arrange + var mask = new ConfigMask { ScriptName = scriptName }; + var config = new Config(); + + // act + config = config.Apply(mask); + + // assert + config.ScriptName.ShouldEqual(expectedScriptName); + } + } + } +} diff --git a/test/ScriptCs.Tests/ExecuteLooseScriptCommandTests.cs b/test/ScriptCs.Tests/ExecuteLooseScriptCommandTests.cs new file mode 100644 index 00000000..b9e17f2d --- /dev/null +++ b/test/ScriptCs.Tests/ExecuteLooseScriptCommandTests.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Moq; +using ScriptCs.Command; +using ScriptCs.Contracts; +using ScriptCs.Hosting; +using Should; +using Xunit; +using AutoFixture.Xunit2; + +namespace ScriptCs.Tests +{ + public class ExecuteLooseScriptCommandTests + { + public class ExecuteMethod + { + [Theory, ScriptCsAutoData] + public void LooseScriptExecCommandShouldInvokeWithScriptPassedFromArgs( + [Frozen] Mock fileSystem, + [Frozen] Mock executor, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) + { + // arrange + var args = new Config { AllowPreRelease = false, PackageName = "", Eval = "foo", }; + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + sut.Execute(); + + // assert + executor.Verify( + i => i.Initialize( + It.IsAny>(), It.IsAny>()), Times.Once()); + + executor.Verify( + i => i.ExecuteScript(It.Is(x => x == "foo"), It.IsAny()), Times.Once()); + + executor.Verify( + i => i.Terminate(), Times.Once()); + } + + [Theory, ScriptCsAutoData] + public void NonManagedAssembliesAreExcluded( + [Frozen] Mock fileSystem, + [Frozen] Mock assemblyUtility, + [Frozen] Mock executor, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) + { + // arrange + const string NonManaged = "non-managed.dll"; + + var args = new Config { AllowPreRelease = false, PackageName = "", Eval = "foo", }; + + fileSystem.Setup( + x => x.EnumerateFiles(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)) + .Returns(new[] { "managed.dll", NonManaged }); + + assemblyUtility.Setup(x => x.IsManagedAssembly(It.Is(y => y == NonManaged))).Returns(false); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + sut.Execute(); + + // assert + executor.Verify( + i => i.Initialize( + It.Is>(x => !x.Contains(NonManaged)), It.IsAny>()), + Times.Once()); + + executor.Verify( + i => i.ExecuteScript(It.Is(x => x == "foo"), It.IsAny()), Times.Once()); + + executor.Verify( + i => i.Terminate(), Times.Once()); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnErrorIfThereIsCompileException( + [Frozen] Mock fileSystem, + [Frozen] Mock executor, + [Frozen] TestLogProvider logProvider, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) + { + // arrange + var args = new Config + { + AllowPreRelease = false, + PackageName = "", + Eval = "foo" + }; + + executor.Setup(i => i.ExecuteScript(It.IsAny(), It.IsAny())) + .Returns(new ScriptResult(compilationException: new Exception("test"))); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + var result = sut.Execute(); + + // assert + result.ShouldEqual(CommandResult.Error); + logProvider.Output.ShouldContain("ERROR:"); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnErrorIfThereIsExecutionException( + [Frozen] Mock fileSystem, + [Frozen] Mock executor, + [Frozen] TestLogProvider logProvider, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) + { + // arrange + var args = new Config + { + AllowPreRelease = false, + PackageName = "", + Eval = "foo" + }; + + executor.Setup(i => i.ExecuteScript(It.IsAny(), It.IsAny())) + .Returns(new ScriptResult(executionException: new Exception("test"))); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + var result = sut.Execute(); + + // assert + result.ShouldEqual(CommandResult.Error); + logProvider.Output.ShouldContain("ERROR:"); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnErrorIfTheScriptIsIncomplete( + [Frozen] Mock fileSystem, + [Frozen] Mock executor, + [Frozen] TestLogProvider logProvider, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) + { + // arrange + var args = new Config { Eval = "foo" }; + + executor.Setup(i => i.ExecuteScript(It.IsAny(), It.IsAny())) + .Returns(ScriptResult.Incomplete); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + var result = sut.Execute(); + + // assert + result.ShouldEqual(CommandResult.Error); + logProvider.Output.ShouldContain("ERROR:"); + } + + [Theory, ScriptCsAutoData] + public void ShouldComposeScripts([Frozen] Mock fileSystem, Mock composer) + { + var cmd = new ExecuteLooseScriptCommand( + null, + null, + fileSystem.Object, + new Mock().Object, + new Mock().Object, + new TestLogProvider(), + new Mock().Object, + composer.Object); + + cmd.Execute(); + + composer.Verify(c => c.Compose(It.IsAny(), null)); + } + + + } + } +} diff --git a/test/ScriptCs.Tests/ExecuteReplCommandTests.cs b/test/ScriptCs.Tests/ExecuteReplCommandTests.cs index 4bb200da..0607feb9 100644 --- a/test/ScriptCs.Tests/ExecuteReplCommandTests.cs +++ b/test/ScriptCs.Tests/ExecuteReplCommandTests.cs @@ -1,17 +1,12 @@ -using System.Collections; +using System; using System.Collections.Generic; using System.Text; using Moq; - -using Ploeh.AutoFixture.Xunit; - using ScriptCs.Command; using ScriptCs.Contracts; - -using Should; - -using Xunit.Extensions; -using System; +using ScriptCs.Hosting; +using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -23,71 +18,113 @@ public class TheExecuteMethod public void ShouldPromptForInput( [Frozen] Mock fileSystem, [Frozen] Mock console, - CommandFactory factory) + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange - var readLines = 0; - var builder = new StringBuilder(); - var args = new ScriptCsArgs { Repl = true }; + // arrange + var config = new Config { Repl = true }; - fileSystem.SetupGet(x => x.CurrentDirectory).Returns("C:\\"); + console.Setup(x => x.ReadLine(It.IsAny())).Returns((string)null); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.SetupGet(b => b.ConsoleInstance).Returns(console.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); - console.Setup(x => x.ReadLine()).Callback(() => readLines++).Throws(new Exception()); - console.Setup(x => x.Write(It.IsAny())).Callback(value => builder.Append(value)); + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(config, new string[0]); - // Act - factory.CreateCommand(args, new string[0]).Execute(); + // act + sut.Execute(); - // Assert - builder.ToString().EndsWith("> ").ShouldBeTrue(); - readLines.ShouldEqual(1); + // assert + console.Verify(x=>x.ReadLine("> ")); } [Theory, ScriptCsAutoData] public void WhenPassedAScript_ShouldPressedReplWithScript( - [Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, [Frozen] Mock console, - CommandFactory factory) + [Frozen] Mock scriptEngine, + [Frozen] Mock console, + [Frozen] Mock fileSystem, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange - var args = new ScriptCsArgs { Repl = true, ScriptName = "test.csx" }; - - console.Setup(x => x.ReadLine()).Returns(() => - { - console.Setup(x => x.ReadLine()).Throws(new Exception()); - return string.Empty; - }); - fileSystem.SetupGet(x => x.CurrentDirectory).Returns("C:\\"); - scriptEngine.Setup( - x => x.Execute("#load test.csx", It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())); - - // Act - factory.CreateCommand(args, new string[0]).Execute(); - - // Assert + // arrange + var args = new Config { Repl = true, ScriptName = "test.csx", }; + + scriptEngine.Setup(x => x.Execute( + "#load test.csx", + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())); + + console.Setup(x => x.ReadLine("")).Throws(new Exception()); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + sut.Execute(); + + // assert scriptEngine.Verify(); } [Theory, ScriptCsAutoData] public void WhenNotPassedAScript_ShouldNotCallTheEngineAutomatically( - [Frozen] Mock scriptEngine, [Frozen] Mock fileSystem, [Frozen] Mock console, - CommandFactory factory) + [Frozen] Mock scriptEngine, + [Frozen] Mock console, + [Frozen] Mock fileSystem, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange - var args = new ScriptCsArgs { Repl = true }; + // arrange + var config = new Config { Repl = true }; + + console.Setup(x => x.ReadLine("")).Throws(new Exception()); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); - console.Setup(x => x.ReadLine()).Returns(() => - { - console.Setup(x => x.ReadLine()).Throws(new Exception()); - return string.Empty; - }); - fileSystem.SetupGet(x => x.CurrentDirectory).Returns("C:\\"); + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(config, new string[0]); - // Act - factory.CreateCommand(args, new string[0]).Execute(); + // act + sut.Execute(); - // Assert + // assert scriptEngine.Verify( - x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never()); + x => x.Execute( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), + Times.Never()); + } + + [Theory, ScriptCsAutoData] + public void ShouldComposeScripts([Frozen] Mock fileSystem, Mock composer) + { + var cmd = new ExecuteScriptCommand( + null, + null, + fileSystem.Object, + new Mock().Object, + new Mock().Object, + new TestLogProvider(), + new Mock().Object, + composer.Object); + + cmd.Execute(); + + composer.Verify(c => c.Compose(It.IsAny(),null)); } } } diff --git a/test/ScriptCs.Tests/ExecuteScriptCommandTests.cs b/test/ScriptCs.Tests/ExecuteScriptCommandTests.cs index 6524a825..9c3c302f 100644 --- a/test/ScriptCs.Tests/ExecuteScriptCommandTests.cs +++ b/test/ScriptCs.Tests/ExecuteScriptCommandTests.cs @@ -1,22 +1,14 @@ using System; using System.Collections.Generic; using System.IO; - -using Common.Logging; - +using System.Linq; using Moq; - -using Ploeh.AutoFixture.Xunit; - using ScriptCs.Command; using ScriptCs.Contracts; - -using System.Linq; -using System.Runtime.ExceptionServices; - +using ScriptCs.Hosting; using Should; - -using Xunit.Extensions; +using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -24,26 +16,37 @@ public class ExecuteScriptCommandTests { public class ExecuteMethod { - private const string CurrentDirectory = "C:\\"; - [Theory, ScriptCsAutoData] public void ScriptExecCommandShouldInvokeWithScriptPassedFromArgs( [Frozen] Mock fileSystem, [Frozen] Mock executor, - CommandFactory factory) + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange - var args = new ScriptCsArgs { AllowPreRelease = false, Install = "", ScriptName = "test.csx" }; + // arrange + var args = new Config { AllowPreRelease = false, PackageName = "", ScriptName = "test.csx", }; - fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); - // Act - factory.CreateCommand(args, new string[0]).Execute(); + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); - // Assert - executor.Verify(i => i.Initialize(It.IsAny>(), It.IsAny>()), Times.Once()); - executor.Verify(i => i.Execute(It.Is(x => x == "test.csx"), It.IsAny()), Times.Once()); - executor.Verify(i => i.Terminate(), Times.Once()); + // act + sut.Execute(); + + // assert + executor.Verify( + i => i.Initialize( + It.IsAny>(), It.IsAny>()), Times.Once()); + + executor.Verify( + i => i.Execute(It.Is(x => x == "test.csx"), It.IsAny()), Times.Once()); + + executor.Verify( + i => i.Terminate(), Times.Once()); } [Theory, ScriptCsAutoData] @@ -51,83 +54,162 @@ public void NonManagedAssembliesAreExcluded( [Frozen] Mock fileSystem, [Frozen] Mock assemblyUtility, [Frozen] Mock executor, - CommandFactory factory) + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange + // arrange const string NonManaged = "non-managed.dll"; - var args = new ScriptCsArgs { AllowPreRelease = false, Install = "", ScriptName = "test.csx" }; + var args = new Config { AllowPreRelease = false, PackageName = "", ScriptName = "test.csx", }; - fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); - fileSystem.Setup(x => x.EnumerateFiles(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)) - .Returns(new[] { "managed.dll", NonManaged }); + fileSystem.Setup( + x => x.EnumerateFiles(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)) + .Returns(new[] { "managed.dll", NonManaged }); assemblyUtility.Setup(x => x.IsManagedAssembly(It.Is(y => y == NonManaged))).Returns(false); + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + sut.Execute(); - // Act - factory.CreateCommand(args, new string[0]).Execute(); + // assert + executor.Verify( + i => i.Initialize( + It.Is>(x => !x.Contains(NonManaged)), It.IsAny>()), + Times.Once()); - // Assert - executor.Verify(i => i.Initialize(It.Is>(x => !x.Contains(NonManaged)), It.IsAny>()), Times.Once()); - executor.Verify(i => i.Execute(It.Is(x => x == "test.csx"), It.IsAny()), Times.Once()); - executor.Verify(i => i.Terminate(), Times.Once()); + executor.Verify( + i => i.Execute(It.Is(x => x == "test.csx"), It.IsAny()), Times.Once()); + + executor.Verify( + i => i.Terminate(), Times.Once()); } [Theory, ScriptCsAutoData] public void ShouldReturnErrorIfThereIsCompileException( [Frozen] Mock fileSystem, [Frozen] Mock executor, - [Frozen] Mock logger, - CommandFactory factory) + [Frozen] TestLogProvider logProvider, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange - var args = new ScriptCsArgs + // arrange + var args = new Config { AllowPreRelease = false, - Install = "", - ScriptName = "test.csx" + PackageName = "", + ScriptName = "test.csx", }; - fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); - executor.Setup(i => i.Execute(It.IsAny(), It.IsAny())) - .Returns(new ScriptResult {CompileExceptionInfo = ExceptionDispatchInfo.Capture(new Exception("test"))}); + .Returns(new ScriptResult(compilationException: new Exception("test"))); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); - // Act - var result = factory.CreateCommand(args, new string[0]).Execute(); + // act + var result = sut.Execute(); - // Assert + // assert result.ShouldEqual(CommandResult.Error); - logger.Verify(i => i.Error(It.IsAny()), Times.Once()); + logProvider.Output.ShouldContain("ERROR:"); } [Theory, ScriptCsAutoData] - public void ShouldReturnErrorIfThereIsExecuteException( + public void ShouldReturnErrorIfThereIsExecutionException( [Frozen] Mock fileSystem, [Frozen] Mock executor, - [Frozen] Mock logger, - CommandFactory factory) + [Frozen] TestLogProvider logProvider, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { - // Arrange - var args = new ScriptCsArgs + // arrange + var args = new Config { AllowPreRelease = false, - Install = "", + PackageName = "", ScriptName = "test.csx" }; - fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); + executor.Setup(i => i.Execute(It.IsAny(), It.IsAny())) + .Returns(new ScriptResult(executionException: new Exception("test"))); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); + + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + var result = sut.Execute(); + + // assert + result.ShouldEqual(CommandResult.Error); + logProvider.Output.ShouldContain("ERROR:"); + } + + [Theory, ScriptCsAutoData] + public void ShouldReturnErrorIfTheScriptIsIncomplete( + [Frozen] Mock fileSystem, + [Frozen] Mock executor, + [Frozen] TestLogProvider logProvider, + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) + { + // arrange + var args = new Config { ScriptName = "test.csx" }; executor.Setup(i => i.Execute(It.IsAny(), It.IsAny())) - .Returns(new ScriptResult { ExecuteExceptionInfo = ExceptionDispatchInfo.Capture(new Exception("test")) }); + .Returns(ScriptResult.Incomplete); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + servicesBuilder.Setup(b => b.Build()).Returns(services); - // Act - var result = factory.CreateCommand(args, new string[0]).Execute(); + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); - // Assert + // act + var result = sut.Execute(); + + // assert result.ShouldEqual(CommandResult.Error); - logger.Verify(i => i.Error(It.IsAny()), Times.Once()); + logProvider.Output.ShouldContain("ERROR:"); } + + [Theory, ScriptCsAutoData] + public void ShouldComposeScripts([Frozen] Mock fileSystem, Mock composer) + { + var cmd = new ExecuteScriptCommand( + null, + null, + fileSystem.Object, + new Mock().Object, + new Mock().Object, + new TestLogProvider(), + new Mock().Object, + composer.Object); + + cmd.Execute(); + + composer.Verify(c=>c.Compose(It.IsAny(),null)); + } + + } } } diff --git a/test/ScriptCs.Tests/InstallCommandTests.cs b/test/ScriptCs.Tests/InstallCommandTests.cs index 854f3d5a..cc1c5393 100644 --- a/test/ScriptCs.Tests/InstallCommandTests.cs +++ b/test/ScriptCs.Tests/InstallCommandTests.cs @@ -2,16 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Versioning; - using Moq; - -using Ploeh.AutoFixture.Xunit; - +using AutoFixture; +using AutoFixture.AutoMoq; using ScriptCs.Command; using ScriptCs.Contracts; -using ScriptCs.Package; - -using Xunit.Extensions; +using ScriptCs.Hosting; +using Xunit; +using AutoFixture.Xunit2; namespace ScriptCs.Tests { @@ -19,42 +17,56 @@ public class InstallCommandTests { public class ExecuteMethod { - private const string CurrentDirectory = @"C:\"; - [Theory, ScriptCsAutoData] public void InstallCommandShouldInstallSinglePackageIfNamePassed( [Frozen] Mock fileSystem, [Frozen] Mock packageInstaller, - CommandFactory factory) + [Frozen] Mock resolver, + [Frozen] Mock initializationServices, + ScriptServices services) { // Arrange - var args = new ScriptCsArgs { AllowPreRelease = false, Install = "mypackage", ScriptName = null }; + var args = new Config { AllowPreRelease = false, PackageName = "mypackage", }; + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var servicesBuilder = fixture.Freeze>(); + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + initializationServices.Setup(i => i.GetPackageInstaller()).Returns(packageInstaller.Object); + initializationServices.Setup(i => i.GetPackageAssemblyResolver()).Returns(resolver.Object); - fileSystem.Setup(x => x.GetWorkingDirectory(It.IsAny())).Returns(CurrentDirectory); - fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); + servicesBuilder.Setup(b => b.Build()).Returns(services); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); + var factory = fixture.Create(); // Act factory.CreateCommand(args, new string[0]).Execute(); // Assert - packageInstaller.Verify(i => - i.InstallPackages( - It.Is>(x => x.Count() == 1 && x.First().PackageId == "mypackage"), It.IsAny()), + packageInstaller.Verify( + i => i.InstallPackages( + It.Is>(x => x.Count() == 1 && x.First().PackageId == "mypackage"), + It.IsAny()), Times.Once()); } [Theory, ScriptCsAutoData] public void InstallCommandShouldInstallFromPackagesConfigIfNoNamePassed( + [Frozen] Mock packageInstaller, [Frozen] Mock fileSystem, [Frozen] Mock resolver, - [Frozen] Mock packageInstaller, - CommandFactory factory) + [Frozen] Mock initializationServices, + [Frozen] Mock servicesBuilder, + ScriptServices services) { // Arrange - var args = new ScriptCsArgs { AllowPreRelease = false, Install = "", ScriptName = null }; + var args = new Config { AllowPreRelease = false, PackageName = string.Empty, }; + + initializationServices.Setup(i => i.GetFileSystem()).Returns(fileSystem.Object); + initializationServices.Setup(i => i.GetPackageInstaller()).Returns(packageInstaller.Object); + initializationServices.Setup(i => i.GetPackageAssemblyResolver()).Returns(resolver.Object); - fileSystem.Setup(x => x.GetWorkingDirectory(It.IsAny())).Returns(CurrentDirectory); - fileSystem.SetupGet(x => x.CurrentDirectory).Returns(CurrentDirectory); + servicesBuilder.Setup(b => b.Build()).Returns(services); + servicesBuilder.SetupGet(b => b.InitializationServices).Returns(initializationServices.Object); resolver.Setup(i => i.GetPackages(It.IsAny())).Returns(new List { @@ -62,8 +74,11 @@ public void InstallCommandShouldInstallFromPackagesConfigIfNoNamePassed( new PackageReference("b", new FrameworkName(".NETFramework,Version=v4.0"), new Version()) }); - // Act - factory.CreateCommand(args, new string[0]).Execute(); + var factory = new CommandFactory(servicesBuilder.Object); + var sut = factory.CreateCommand(args, new string[0]); + + // act + sut.Execute(); // Assert packageInstaller.Verify(i => i.InstallPackages(It.Is>(x => x.Count() == 2), It.IsAny()), Times.Once()); diff --git a/test/ScriptCs.Tests/Properties/AssemblyInfo.cs b/test/ScriptCs.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 3180a546..00000000 --- a/test/ScriptCs.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("ScriptCs.Tests")] -[assembly: AssemblyDescription("")] - -[assembly: Guid("96733f0d-6fa4-4f6d-8f69-e4718eaf48b7")] diff --git a/test/ScriptCs.Tests/ScriptArgsTests.cs b/test/ScriptCs.Tests/ScriptArgsTests.cs deleted file mode 100644 index 5c1e7fe4..00000000 --- a/test/ScriptCs.Tests/ScriptArgsTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using ScriptCs.Argument; -using Should; -using Xunit; - -namespace ScriptCs.Tests -{ - public class ScriptArgsTests - { - public class SplitScriptArgsMethod - { - [Fact] - public void ShouldHandleEmptyArgs() - { - var args = new string[0]; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new string[0]); - sr.ScriptArguments.ShouldEqual(new string[0]); - } - - [Fact] - public void ShouldHandleMissingDoubledash() - { - var args = new string[] { "scriptname.csx", "-restore" }; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new[] { "scriptname.csx", "-restore" }); - sr.ScriptArguments.ShouldEqual(new string[0]); - } - - [Fact] - public void ShouldHandleArgsAndScriptArgs() - { - var args = new string[] { "scriptname.csx", "-restore", "--", "-port", "8080" }; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new[] { "scriptname.csx", "-restore" }); - sr.ScriptArguments.ShouldEqual(new[] { "-port", "8080" }); - } - - [Fact] - public void ShouldHandleJustScriptArgs() - { - var args = new string[] { "--", "-port", "8080" }; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new string[0]); - sr.ScriptArguments.ShouldEqual(new[] { "-port", "8080" }); - } - - [Fact] - public void ShouldHandleJustDoubledash() - { - var args = new string[] { "--" }; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new string[0]); - sr.ScriptArguments.ShouldEqual(new string[0]); - } - - [Fact] - public void ShouldHandleExtraDoubledash() - { - var args = new string[] { "scriptname.csx", "-restore", "--", "-port", "--", "8080" }; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new[] { "scriptname.csx", "-restore" }); - sr.ScriptArguments.ShouldEqual(new[] { "-port", "--", "8080" }); - } - - [Fact] - public void ShouldHandleTrailingDoubledash() - { - var args = new string[] { "scriptname.csx", "-restore", "--" }; - - var sr = ArgumentHandler.SplitScriptArgs(args); - - sr.CommandArguments.ShouldEqual(new[] { "scriptname.csx", "-restore" }); - sr.ScriptArguments.ShouldEqual(new string[0]); - } - } - } -} \ No newline at end of file diff --git a/test/ScriptCs.Tests/ScriptCs.Tests.csproj b/test/ScriptCs.Tests/ScriptCs.Tests.csproj index 8e8ac0f1..25bde541 100644 --- a/test/ScriptCs.Tests/ScriptCs.Tests.csproj +++ b/test/ScriptCs.Tests/ScriptCs.Tests.csproj @@ -1,103 +1,27 @@ - - - + - {4D6A2A55-BB17-40CB-9567-EAFF80BEFDCE} - Library - ScriptCs.Tests - ScriptCs.Tests - ..\..\ - ..\..\ScriptCs.Test.ruleset + net461 - - False - ..\..\packages\Common.Logging.2.1.2\lib\net40\Common.Logging.dll - - - ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - False - ..\..\packages\AutoFixture.3.6.5\lib\net40\Ploeh.AutoFixture.dll - - - False - ..\..\packages\AutoFixture.AutoMoq.3.6.5\lib\net40\Ploeh.AutoFixture.AutoMoq.dll - - - False - ..\..\packages\AutoFixture.Xunit.3.6.5\lib\net40\Ploeh.AutoFixture.Xunit.dll - - - False - ..\..\packages\Should.1.1.20\lib\Should.dll - - - - - - - - - - - - - ..\..\packages\xunit.1.9.1\lib\net20\xunit.dll - - - ..\..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll - + + + - - Properties\CommonAssemblyInfo.cs - - - Properties\CommonVersionInfo.cs - - - ScriptCsAutoDataAttribute.cs - - - - - - - - - - - - + + + + + + - - {6049E205-8B5F-4080-B023-70600E51FD64} - ScriptCs.Contracts - - - {E590E710-E159-48E6-A3E6-1A83D3FE732C} - ScriptCs.Core - - - {E79EC231-E27D-4057-91C9-2D001A3A8C3B} - ScriptCs.Engine.Roslyn - - - {9AEF2D95-87FB-4829-B384-34BFE076D531} - ScriptCs.Hosting - - - {25080671-1a80-4041-b9c7-260578ff4849} - ScriptCs - + + + + - - + - - \ No newline at end of file diff --git a/test/ScriptCs.Tests/VersionCommandTests.cs b/test/ScriptCs.Tests/VersionCommandTests.cs deleted file mode 100644 index 75648343..00000000 --- a/test/ScriptCs.Tests/VersionCommandTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Diagnostics; -using System.Reflection; - -using Moq; - -using Ploeh.AutoFixture.Xunit; - -using ScriptCs.Command; -using ScriptCs.Contracts; - -using Xunit.Extensions; - -namespace ScriptCs.Tests -{ - public class VersionCommandTests - { - public class ExecuteMethod - { - [Theory, ScriptCsAutoData] - public void VersionCommandShouldOutputVersion([Frozen] Mock console, CommandFactory factory) - { - // Arrange - var args = new ScriptCsArgs { Version = true }; - - var assembly = typeof(ScriptCsArgs).Assembly; - var currentVersion = FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion; - - // Act - factory.CreateCommand(args, new string[0]).Execute(); - - // Assert - console.Verify(x => x.WriteLine(It.Is(y => y.Contains(currentVersion.ToString())))); - } - } - } -} diff --git a/test/ScriptCs.Tests/app.config b/test/ScriptCs.Tests/app.config index 50b7db70..8cea6cbd 100644 --- a/test/ScriptCs.Tests/app.config +++ b/test/ScriptCs.Tests/app.config @@ -3,25 +3,53 @@ - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - \ No newline at end of file + diff --git a/test/ScriptCs.Tests/packages.config b/test/ScriptCs.Tests/packages.config deleted file mode 100644 index f9b0001b..00000000 --- a/test/ScriptCs.Tests/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/test/ScriptCsAutoDataAttribute.cs b/test/ScriptCsAutoDataAttribute.cs index 992919be..cdbc5b65 100644 --- a/test/ScriptCsAutoDataAttribute.cs +++ b/test/ScriptCsAutoDataAttribute.cs @@ -1,13 +1,24 @@ -using Ploeh.AutoFixture; -using Ploeh.AutoFixture.AutoMoq; -using Ploeh.AutoFixture.Xunit; +using AutoFixture; +using AutoFixture.AutoMoq; +using AutoFixture.Xunit2; +using Xunit; namespace ScriptCs.Tests { - public class ScriptCsAutoDataAttribute : AutoDataAttribute + public class ScriptCsAutoDataAttribute : CompositeDataAttribute { - public ScriptCsAutoDataAttribute() - : base(new Fixture().Customize(new AutoMoqCustomization())) + public ScriptCsAutoDataAttribute(params object[] values) + : base( + new InlineDataAttribute(values), + new InternalScriptCsAutoDataAttribute() + ) + { + } + } + + public class InternalScriptCsAutoDataAttribute : AutoDataAttribute + { + public InternalScriptCsAutoDataAttribute() : base(() => new Fixture().Customize(new ScriptCsMoqCustomization())) { } } diff --git a/test/ScriptCsMoqCustomization.cs b/test/ScriptCsMoqCustomization.cs new file mode 100644 index 00000000..51467257 --- /dev/null +++ b/test/ScriptCsMoqCustomization.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using Moq; +using AutoFixture; +using AutoFixture.AutoMoq; +using ScriptCs.Contracts; + +namespace ScriptCs.Tests +{ + public class ScriptCsMoqCustomization : AutoMoqCustomization, ICustomization + { + void ICustomization.Customize(IFixture fixture) + { + this.Customize(fixture); + + fixture.Register(() => + { + var fileSystem = new Mock(); + fileSystem.SetupGet(f => f.PackagesFile).Returns("scriptcs_packages.config"); + fileSystem.SetupGet(f => f.PackagesFolder).Returns("scriptcs_packages"); + fileSystem.SetupGet(f => f.BinFolder).Returns("scriptcs_bin"); + fileSystem.SetupGet(f => f.DllCacheFolder).Returns(".scriptcs_cache"); + fileSystem.SetupGet(f => f.NugetFile).Returns("scriptcs_nuget.config"); + fileSystem.SetupGet(f => f.CurrentDirectory).Returns("workingdirectory"); + fileSystem.Setup(f => f.FileExists(@"workingdirectory\scriptcs_packages\PackageScripts.csx")) + .Returns(false); + fileSystem.Setup(f => f.DirectoryExists(@"workingdirectory\scriptcs_packages")).Returns(true); + fileSystem.Setup(f => f.GetWorkingDirectory(It.IsAny())).Returns("workingdirectory"); + return fileSystem; + }); + + fixture.Register(() => + { + var composer = new Mock(); + composer.SetupGet(c => c.ScriptLibrariesFile).Returns("ScriptLibraries.csx"); + return composer; + }); + + var logProvider = new TestLogProvider(); + fixture.Register(() => logProvider); + fixture.Register(() => logProvider); + + fixture.Register(() => new AppDomainAssemblyResolver( + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create>())); + + fixture.Register(() => new ScriptLibraryComposer( + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create())); + + fixture.Register(() => new ScriptServices( + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create>(), + fixture.Create(), + fixture.Create(), + fixture.Create())); + + fixture.Register(() => new ScriptExecutor( + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + fixture.Create(), + new ScriptInfo())); + } + } +} diff --git a/test/TestLogProvider.cs b/test/TestLogProvider.cs new file mode 100644 index 00000000..9348134a --- /dev/null +++ b/test/TestLogProvider.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using System.Text; + +namespace ScriptCs.Tests +{ + using ScriptCs.Contracts; + + public class TestLogProvider : ILogProvider + { + private static readonly Disposable disposable = new Disposable(); + private readonly StringBuilder _output = new StringBuilder(); + + public string Output + { + get { return _output.ToString(); } + } + + public Logger GetLogger(string name) + { + return Log; + } + + public IDisposable OpenNestedContext(string message) + { + return disposable; + } + + public IDisposable OpenMappedContext(string key, string value) + { + return disposable; + } + + private bool Log( + LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters) + { + if (messageFunc != null) + { + var line = string.Format( + CultureInfo.InvariantCulture, + "{0}: {1}", + logLevel.ToString().ToUpper(), + string.Format(CultureInfo.InvariantCulture, messageFunc(), formatParameters)); + + _output.AppendLine(line); + } + + return true; + } + + private sealed class Disposable : IDisposable + { + public void Dispose() + { + } + } + } +} diff --git a/tools/xunit/xunit.runner.msbuild.dll b/tools/xunit/xunit.runner.msbuild.dll deleted file mode 100644 index da6cff1e..00000000 Binary files a/tools/xunit/xunit.runner.msbuild.dll and /dev/null differ diff --git a/tools/xunit/xunit.runner.utility.dll b/tools/xunit/xunit.runner.utility.dll deleted file mode 100644 index 8c0e61c3..00000000 Binary files a/tools/xunit/xunit.runner.utility.dll and /dev/null differ