Commit 81229c89 authored by Nathan Bean's avatar Nathan Bean
Browse files

Finalized minimum viable prototype

parent fb949bcf
......@@ -11,8 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProgramVerifier", "Verifier
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeAnalyzers", "CodeAnalyzers\CodeAnalyzers\CodeAnalyzers.csproj", "{65E28062-2B9A-4BB5-B546-449C4CB915E0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationService", "ValidationService\ValidationService.csproj", "{617A0844-8FF5-44B7-ADC9-34C73330664C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -53,14 +51,6 @@ Global
{65E28062-2B9A-4BB5-B546-449C4CB915E0}.Release|Any CPU.Build.0 = Release|Any CPU
{65E28062-2B9A-4BB5-B546-449C4CB915E0}.Release|x64.ActiveCfg = Release|Any CPU
{65E28062-2B9A-4BB5-B546-449C4CB915E0}.Release|x64.Build.0 = Release|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Debug|x64.ActiveCfg = Debug|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Debug|x64.Build.0 = Debug|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Release|Any CPU.Build.0 = Release|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Release|x64.ActiveCfg = Release|Any CPU
{617A0844-8FF5-44B7-ADC9-34C73330664C}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
......@@ -28,6 +28,7 @@ namespace KSU.CS.Pendant.Server.Controllers
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly DataContext _context;
private readonly IGitHubService _gitHubService;
private readonly IAssignmentValidationService _assignmentValidationService;
private readonly string _redirectURI;
private readonly string _clientID;
......@@ -108,9 +109,10 @@ namespace KSU.CS.Pendant.Server.Controllers
/// <param name="dataContext">The data context for the site</param>
/// <param name="configuration">The website configuration</param>
/// <param name="httpClientFactory">A factory for creating shared HTTP clients</param>
public GitHubController(DataContext dataContext, IConfiguration configuration, IHttpClientFactory httpClientFactory, IAssignmentValidationService assignmentValidationService)
public GitHubController(DataContext dataContext, IConfiguration configuration, IHttpClientFactory httpClientFactory, IGitHubService gitHubService, IAssignmentValidationService assignmentValidationService)
{
_context = dataContext;
_gitHubService = gitHubService;
_assignmentValidationService = assignmentValidationService;
_redirectURI = $"{configuration["GitHub:ServiceHost"]}Authorize";
_clientID = configuration["GitHub:ClientID"];
......@@ -325,12 +327,27 @@ namespace KSU.CS.Pendant.Server.Controllers
return StatusCode(404, $"The branch {branch} does not match one of the expected milestone branches: {milestones}");
}
// Clone the supplied repository and check out the appropriate commit
var studentPath = await _gitHubService.CloneAndCheckoutAsync(payload.Repository.FullName, payload.CommitID);
var attempt = new ValidationAttempt()
{
Assignment = milestone,
User = gitHubAccount.User,
SolutionPath = studentPath,
RepoUrl = $"https://github.com/{payload.Repository.FullName}",
CommitIdentifier = payload.CommitID
};
// Run the validation against the student project
var issueCount = await _assignmentValidationService.CheckAsync(attempt);
// Fire and forget the validation process - this allows us to return our status code *now*
// while the validation process continues on a backgroudn thread
await _assignmentValidationService.CheckMilestoneAsync(gitHubAccount.User, milestone);
// await _assignmentValidationService.CheckMilestoneAsync(gitHubAccount.User, milestone);
// If we reach this point, the validation process was started successfully - so report a status of 200
return StatusCode(200);
return StatusCode(200, $"{issueCount} issues found");
}
......
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
......@@ -58,5 +59,11 @@ namespace KSU.CS.Pendant.Server.Models
/// The location of the student's work in the file system
/// </summary>
public string SolutionPath { get; set; }
[NotMapped]
public string RepoUrl { get; set; }
[NotMapped]
public string CommitIdentifier { get; set; }
}
}
......@@ -23,6 +23,58 @@ namespace KSU.CS.Pendant.Server.Services
#region Interface Methods
/// <summary>
/// Checks the <paramref name="milestone"/> assignment for the <paramref name="user"/>,
/// generating a new Validation that describes what work still needs to be completed.
/// </summary>
/// <param name="user">The user whose milestone submission should be checked</param>
/// <param name="milestone">The milestone the submission is for</param>
public int Check(ValidationAttempt attempt)
{
return CheckAsync(attempt).GetAwaiter().GetResult();
}
/// <summary>
/// Checks the <paramref name="milestone"/> assignment for the <paramref name="user"/>,
/// generating a new Validation that describes what work still needs to be completed.
/// </summary>
/// <param name="user">The user whose milestone submission should be checked</param>
/// <param name="milestone">The milestone the submission is for</param>
public async Task<int> CheckAsync(ValidationAttempt attempt)
{
// Validate the assignment
var result = await ProgramVerifier.Verifier.Check(attempt.SolutionPath, attempt.Assignment.SpecificationPath);
// Populate the ValidationAttempt
attempt.DateTime = DateTime.Now;
attempt.StructuralIssues = result.StructuralIssues.Select(issue => new ValidationIssue()
{
Message = issue
// TODO: add assignment url for HelpUrl
}).ToList();
attempt.FunctionalIssues = result.FunctionalIssues.Select(issue => new ValidationIssue()
{
Message = issue
}).ToList();
attempt.DesignIssues = result.DesignIssues.Select(issue => new ValidationIssue()
{
Message = issue.Message,
HelpUrl = issue.HelpUrl,
SourceUrl = BuildGitHubSourceUrl(attempt.RepoUrl, attempt.CommitIdentifier, issue.Path, issue.Line)
}).ToList();
attempt.StyleIssues = result.StyleIssues.Select(issue => new ValidationIssue()
{
Message = issue.Message,
HelpUrl = issue.HelpUrl,
SourceUrl = BuildGitHubSourceUrl(attempt.RepoUrl, attempt.CommitIdentifier, issue.Path, issue.Line)
}).ToList();
_context.ValidationAttempts.Add(attempt);
await _context.SaveChangesAsync();
return attempt.IssueCount;
}
/// <summary>
/// Checks the <paramref name="milestone"/> assignment for the <paramref name="user"/>,
/// generating a new Validation that describes what work still needs to be completed.
......@@ -206,6 +258,20 @@ namespace KSU.CS.Pendant.Server.Services
return sb.ToString();
}
private static string BuildGitHubSourceUrl(string repoUrl, string commitIdentifier, string path, string line)
{
if (path.Length == 0) return "";
// Build the souceURL
var sb = new StringBuilder();
sb.Append(repoUrl);
sb.Append("/tree/");
sb.Append(commitIdentifier);
sb.Append(path);
sb.Append("#L");
sb.Append(line);
return sb.ToString();
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace KSU.CS.Pendant.Server.Services
{
public class GitHubService : IGitHubService
{
private readonly IConfiguration _configuration;
public GitHubService(IConfiguration configuration)
{
_configuration = configuration;
}
public string CloneAndCheckout(string repoUrl, string commitId)
{
return CloneAndCheckoutAsync(repoUrl, commitId).GetAwaiter().GetResult();
}
public async Task<string> CloneAndCheckoutAsync(string repoUrl, string commitId)
{
var path = $"{ _configuration.GetValue<string>("Paths:HomeDir") }/{repoUrl}/{commitId}";
var psi = new ProcessStartInfo()
{
FileName = "cmd.exe",
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
// May need to set the user
var password = new SecureString();
foreach (char c in "insecurepassword123") password.AppendChar(c);
psi.UserName = "nhbean_local";
#pragma warning disable CA1416 // Validate platform compatibility
psi.Password = password;
#pragma warning restore CA1416 // Validate platform compatibility
using var process = Process.Start(psi);
using StreamWriter writer = process.StandardInput;
if (writer.BaseStream.CanWrite)
{
await writer.WriteLineAsync($"git clone git@github.com:{repoUrl}.git {path}");
await writer.WriteLineAsync($"cd {path}");
await writer.WriteLineAsync($"git checkout {commitId}");
await writer.WriteLineAsync("exit");
}
await process.WaitForExitAsync();
var output = await process.StandardOutput.ReadToEndAsync();
var errors = await process.StandardError.ReadToEndAsync();
//await File.AppendAllTextAsync("C:\\workDirectory\\log.txt", output);
//await File.AppendAllTextAsync("C:\\workDirectory\\log.txt", errors);
return path;
}
}
}
......@@ -11,6 +11,10 @@ namespace KSU.CS.Pendant.Server.Services
/// </summary>
public interface IAssignmentValidationService
{
public int Check(ValidationAttempt attempt);
public Task<int> CheckAsync(ValidationAttempt attempt);
/// <summary>
/// Saves the supplied <paramref name="assignment"/>'s specification to
/// the file system.
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace KSU.CS.Pendant.Server.Services
{
public interface IGitHubService
{
string CloneAndCheckout(string repoUrl, string commitId);
Task<string> CloneAndCheckoutAsync(string repoUrl, string commitId);
}
}
......@@ -61,6 +61,8 @@ namespace KSU.CS.Pendant.Server
services.AddSession();
services.AddScoped<IGitHubService, GitHubService>();
services.AddScoped<IAssignmentValidationService, AssignmentValidationService>();
services.AddSingleton<IMarkdownService, MarkdownService>();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment