Skip to content
This repository has been archived by the owner on Apr 14, 2022. It is now read-only.

Fix tests on Mac #265

Merged
merged 19 commits into from
Oct 16, 2018
16 changes: 13 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,19 @@ On Windows you can also attach from Visual Studio 2017 (Debug | Attach To Proces
3. Use the `Launch Extension` launch option.

### Unit Tests
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
1. Run the Unit Tests in VS Code via the `Launch Language Server Tests`.
2. On Windows Open PLS.sln solution in Visual Studio 2017 and run its tests.
To run unit tests, do one of the following:
- Run the Unit Tests in the [VS Code Python Extension](https://github.com/Microsoft/vscode-python) project via the `Launch Language Server Tests`.
- On Windows: open PLS.sln solution in Visual Studio 2017 and run tests from the Test Explorer.
- Run `dotnet test` from Terminal in the `src` directory or from `src/Analysis/Engine/Test` if you prefer not to see messages about projects that do not contain test code.
- Install C# extension and .NET Core Test Explorer for VS Code, open src folder in VS Code and run tests.

NOTE: Language Server does not automatically discover Python installations on various operating systems.
At run time path to the Python interpreter is provided by the client application. Test environment does
make an attempt to discover Python installation, but in case it is unable to find Python you will not
be able to run tests. Refer to the Python interpreter discovery code for
[Windows](https://github.com/Microsoft/python-language-server/blob/master/src/Analysis/Engine/Test/WindowsPythonInstallPathResolver.cs)
and [*nix](https://github.com/Microsoft/python-language-server/blob/master/src/Analysis/Engine/Test/UnixPythonInstallPathResolver.cs)


### Coding Standards
Import Formatting.vssettings into Visual Studio
Import `Formatting.vssettings` into Visual Studio or use `.editorconfig`.
121 changes: 71 additions & 50 deletions src/Analysis/Engine/Impl/Infrastructure/PathEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace Microsoft.PythonTools.Analysis.Infrastructure {
class PathEqualityComparer : IEqualityComparer<string> {
public static readonly PathEqualityComparer Instance = new PathEqualityComparer();
public static readonly PathEqualityComparer Instance = new PathEqualityComparer(
RuntimeInformation.IsOSPlatform(OSPlatform.Linux), Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
);

private static readonly char[] InvalidPathChars = GetInvalidPathChars();

Expand All @@ -40,68 +43,87 @@ internal class CacheItem {
private long _accessCount;
private const int CACHE_UPPER_LIMIT = 32;

private readonly StringComparer Ordinal = StringComparer.Ordinal;
private readonly bool _isCaseSensitivePath;
private readonly char _directorySeparator;
private readonly char _altDirectorySeparator;
private readonly char[] _directorySeparators;
private readonly StringComparer _stringComparer;

internal PathEqualityComparer(bool isCaseSensitivePath, char directorySeparator, char altDirectorySeparator = '\0') {
_isCaseSensitivePath = isCaseSensitivePath;
_directorySeparator = directorySeparator;
_altDirectorySeparator = altDirectorySeparator;

if (altDirectorySeparator != '\0' && altDirectorySeparator != directorySeparator) {
_directorySeparators = new[] { directorySeparator, altDirectorySeparator };
} else {
_directorySeparators = new[] { directorySeparator };
}

internal PathEqualityComparer() { }
_stringComparer = _isCaseSensitivePath ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
}

internal static string GetCompareKeyUncached(string path) {
internal string GetCompareKeyUncached(string path) {
if (string.IsNullOrEmpty(path)) {
return path;
}

string root = "";

int rootParts = 0;
var root = string.Empty;
var rootParts = 0;
var parts = new List<string>();
int next_i = int.MaxValue;
for (int i = 0; next_i > 0 && i < path.Length; i = next_i) {
next_i = path.IndexOfAny(PathUtils.DirectorySeparators, i) + 1;

var next_i = path.IndexOfAny(_directorySeparators) + 1;
// Check roots
if (next_i > 2 && next_i < path.Length - 1 && path[next_i - 2] == ':' && path[next_i] == '/') {
// smb://computer/share
next_i++;
root = path.Substring(0, next_i);
}
if (_directorySeparator == '\\') {
// Windows
if (next_i == 3 && char.IsLetter(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
// Windows root like C:\
root = path.Substring(0, next_i).Replace('/', '\\');
}
if (next_i == 1 && (path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/')) {
// Windows UNC
next_i = 2;
root = @"\\";
// Segment 1: 'computer'
// Segment 2: 'share'
rootParts = 2;
}
}
if (_directorySeparator == '/' && next_i == 1 && path[0] == '/') {
root = "/";
}

for (var i = next_i; i < path.Length; i = next_i) {
string segment;
next_i = path.IndexOfAny(_directorySeparators, i) + 1;

if (next_i <= 0) {
segment = path.Substring(i);
next_i = int.MaxValue;
} else {
segment = path.Substring(i, next_i - i - 1);
}
if (segment.Length == 0) {
if (i == 0 && next_i == 1) {
// Slash at the start should be preserved, but we may have
// an SMB reference, so we need to check that too
if (path.IndexOfAny(PathUtils.DirectorySeparators, next_i) != next_i) {
parts.Add(string.Empty);
continue;
}

// There are two slashes, so our first four segments will
// be protected:
//
// \\computer\share
//
// Segment 1: '' before first \
// Segment 2: '' between first and second \
// Segment 3: 'computer'
// Segment 4: 'share'
parts.Add(string.Empty);
parts.Add(string.Empty); // the second one will be skipped
rootParts = 4;
}
} else if (segment == ".") {
if (segment == ".") {
// Do nothing
} else if (segment == "..") {
if (parts.Count > rootParts) {
parts.RemoveAt(parts.Count - 1);
} else {
parts.Add(segment);
rootParts += 1;
}
} else {
if (parts.Count == 0 && segment.Last() == ':') {
rootParts = 1;
} else if (segment.Length > 0) {
segment = segment.TrimEnd('.', ' ', '\t');
if (!_isCaseSensitivePath) {
segment = segment.ToUpperInvariant();
}
parts.Add(segment.TrimEnd('.', ' ', '\t').ToUpperInvariant());
parts.Add(segment);
}
}

return root + string.Join(Path.DirectorySeparatorChar.ToString(), parts);
return root + string.Join(_directorySeparator.ToString(), parts);
}

internal CacheItem GetOrCreateCacheItem(string key, out bool created) {
Expand Down Expand Up @@ -136,7 +158,7 @@ internal CacheItem GetOrCreateCacheItem(string key, out bool created) {

private string GetCompareKey(string path) {
var item = GetOrCreateCacheItem(path, out bool created);

string result;
lock (item) {
if (created) {
Expand Down Expand Up @@ -169,19 +191,18 @@ public bool StartsWith(string x, string prefix, bool allowFullMatch = true) {
prefix = GetCompareKey(prefix);
x = GetCompareKey(x);

if (Ordinal.Equals(prefix, x)) {
if (_stringComparer.Equals(prefix, x)) {
return allowFullMatch;
}

return x.StartsWithOrdinal(prefix + Path.DirectorySeparatorChar);
return x.StartsWith(prefix + _directorySeparator,
_isCaseSensitivePath ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
}

public bool Equals(string x, string y) {
return Ordinal.Equals(GetCompareKey(x), GetCompareKey(y));
}
public bool Equals(string x, string y)
=> _stringComparer.Equals(GetCompareKey(x), GetCompareKey(y));

public int GetHashCode(string obj) {
return Ordinal.GetHashCode(GetCompareKey(obj));
}
public int GetHashCode(string obj)
=> _stringComparer.GetHashCode(GetCompareKey(obj));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ private static InterpreterConfiguration GetConfiguration(Version version, string
return new InterpreterConfiguration(
"AnalysisOnly|{0}|{1}".FormatInvariant(version, description),
description,
arch: InterpreterArchitecture.Unknown,
version: version,
uiMode: InterpreterUIMode.CannotBeDefault | InterpreterUIMode.CannotBeConfigured
architecture: InterpreterArchitecture.Unknown,
version: version
);
}

Expand Down
22 changes: 4 additions & 18 deletions src/Analysis/Engine/Impl/Interpreter/Ast/AstModuleResolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private async Task<IPythonModule> ImportFromTypeStubsAsync(string name, TryImpor
return null;
}

_log?.Log(TraceLevel.Verbose, "ImportTypeStub", mp?.FullName, FastRelativePath(mp?.SourceFile));
_log?.Log(TraceLevel.Verbose, "ImportTypeStub", mp?.FullName, mp?.SourceFile);
return PythonModuleLoader.FromTypeStub(context.Interpreter, mp?.SourceFile, _configuration.Version.ToLanguageVersion(), mp?.FullName);
}

Expand Down Expand Up @@ -327,7 +327,7 @@ private IPythonModule ImportFromBuiltins(string name, AstBuiltinsPythonModule bu
return null;
}

_log?.Log(TraceLevel.Info, "ImportBuiltins", name, FastRelativePath(_configuration.InterpreterPath));
_log?.Log(TraceLevel.Info, "ImportBuiltins", name, _configuration.InterpreterPath);

try {
return new AstBuiltinPythonModule(name, _configuration.InterpreterPath);
Expand Down Expand Up @@ -401,10 +401,10 @@ private async Task<IPythonModule> ImportFromSearchPathsAsync(string name, TryImp
IPythonModule module;

if (mp.IsCompiled) {
_log?.Log(TraceLevel.Verbose, "ImportScraped", mp.FullName, FastRelativePath(mp.SourceFile));
_log?.Log(TraceLevel.Verbose, "ImportScraped", mp.FullName, mp.SourceFile);
module = new AstScrapedPythonModule(mp.FullName, mp.SourceFile);
} else {
_log?.Log(TraceLevel.Verbose, "Import", mp.FullName, FastRelativePath(mp.SourceFile));
_log?.Log(TraceLevel.Verbose, "Import", mp.FullName, mp.SourceFile);
module = PythonModuleLoader.FromFile(context.Interpreter, mp.SourceFile, _configuration.Version.ToLanguageVersion(), mp.FullName);
}

Expand Down Expand Up @@ -447,19 +447,5 @@ internal static async Task<ModulePath> FindModuleAsync(IPythonInterpreterFactory
return default(ModulePath);
}
}

private string FastRelativePath(string fullPath) {
if (string.IsNullOrEmpty(fullPath)) {
return fullPath;
}
if (!fullPath.StartsWithOrdinal(_configuration.PrefixPath, ignoreCase: true)) {
return fullPath;
}
var p = fullPath.Substring(_configuration.PrefixPath.Length);
if (p.Length > 0 && p[0] == Path.DirectorySeparatorChar) {
return p.Substring(1);
}
return p;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public void Imported(IModuleContext context) {
using (var proc = new ProcessHelper(
fact.Configuration.InterpreterPath,
args,
fact.Configuration.PrefixPath
fact.Configuration.LibraryPath
)) {
proc.StartInfo.StandardOutputEncoding = Encoding.UTF8;
proc.OnOutputLine = sw.WriteLine;
Expand Down
Loading