Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution for Assignment2. #66

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
237 changes: 237 additions & 0 deletions solutions/Abhilasha/Assignment2/BuildAutomationTool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# -*- coding: utf-8 -*-

"""
Created on Fri Dec 6 14:36:59 2019
@author: Abhilasha

"""

import os
import json
import subprocess
from pathlib import Path
from os.path import relpath
import time
from enum import Enum


class Status(Enum):

not_started = 1
ready = 2
processing = 3
done = 4


class ActionGraph():

'''loads commands, dependencies and initialises the status for all the actions from all build.json files.'''

def __init__(self):

self.all_action_commands = {}
self.all_action_dependencies = {}


def create_action_map(self, root_dir):

for filename in Path(root_dir).rglob('build.json'):
path_elements = str(relpath(filename, root_dir)).split(os.path.sep)
rel_location = os.path.sep.join(path_elements[:-1])
with open(filename) as json_file:
all_data = json.load(json_file)
kaustubh-karkare marked this conversation as resolved.
Show resolved Hide resolved
for data in all_data:
if rel_location != '':
action = rel_location+'/'+data['name']
else:
action = data['name']
self.all_action_commands[action] = data.get('command')
self.all_action_dependencies[action] = data.get('deps')
return self.all_action_commands, self.all_action_dependencies


class Action():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the correct name.
An action is a specific thing you can execute.
This class is responsible for executing multiple actions.
So "ActionExecutor" might be a better name.


def __init__(self):

self.current_action_status = {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action status should be stored in the ActionExecutor, not here, since that is the class that cares about it.

self.current_action_commands = {}
self.current_action_dependencies = {}
self.dependents = {}


def action_sequence(self, root, action):

graph = ActionGraph()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action = single item
ActionGraph = a collection of actions.

ActionGraph can use Action, but Action cannot use ActionGraph like this.

self.all_action_commands, self.all_action_dependencies = graph.create_action_map(root)

if action not in self.all_action_commands:
raise Exception('Command not recognized.')

self.get_dependencies(action)
self.store_dependents_for_all_action()
self.initialize_dependencies_status(action)

return (self.current_action_status,
self.current_action_commands,
self.current_action_dependencies,
self.dependents)


def get_dependencies(self, action):

'''stores the status, commands, dependencies of the action to be executed and all its dependencies'''

self.current_action_status[action] = Status.not_started
self.current_action_commands[action] = self.all_action_commands[action]
self.current_action_dependencies[action] = self.all_action_dependencies[action]

if not self.all_action_dependencies[action] is None:
for name in self.all_action_dependencies[action]:
self.get_dependencies(name)
return


def store_dependents_for_all_action(self):

for action in self.current_action_status:
self.dependents.setdefault(action, [])

for action in self.current_action_status:
self.action_name = action
self.get_dependents(action)

return


def get_dependents(self, action):

if not self.all_action_dependencies[action] is None:
for dep in self.all_action_dependencies[action]:
if self.action_name not in self.dependents[dep]:
self.dependents[dep].append(self.action_name)
self.get_dependents(dep)

return


def initialize_dependencies_status(self, action):

''' initialize the status of the actions with no deps as ready '''
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, the status is relevant only for ActionExecutor.


if self.current_action_dependencies[action] is None:
self.current_action_status[action] = Status.ready

else:
for dep in self.current_action_dependencies[action]:
self.initialize_dependencies_status(dep)

return


class ActionExecutor():

def __init__(self, root_dir):

self.root_dir = root_dir
self.ongoing_subprocesses = []
self.action_name_for_ongoing_subprocess = []
self.action_name = ''
self.pending_actions = []


def execute(self, build, action):

if build != 'build':
raise Exception('Command not recognized.')
Comment on lines +145 to +146
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, why do you need this at all?


action_obj = Action()
(self.current_action_status,
self.current_action_commands,
self.current_action_dependencies,
self.dependents) = action_obj.action_sequence(self.root_dir, action)

self.initialize_pending_actions()
self.execute_commands()

return


def initialize_pending_actions(self):

for action in self.current_action_status:
self.pending_actions.append(action)

return


def update_dependencies_status(self, action):

''' updates the status of the actions with all deps executed as ready '''

total_no_of_dependencies = len(self.current_action_dependencies[action])
no_of_dependencies_done = 0
for dep in self.current_action_dependencies[action]:
if self.current_action_status[dep] == Status.done:
no_of_dependencies_done += 1
if no_of_dependencies_done == total_no_of_dependencies:
self.current_action_status[action] = Status.ready

return


def execute_commands(self):

while len(self.pending_actions) != 0:
any_action_has_no_command = False
for name in self.current_action_status:
if self.current_action_status[name] == Status.ready:
self.current_action_status[name] = Status.processing
cwd = os.getcwd()
if '/' in name:
loc = name[:name.rindex('/')]
else:
loc = ''
if loc != '':
cwd = cwd+os.path.sep+loc
command = self.current_action_commands[name]
if command is None:
any_action_has_no_command = True
self.current_action_status[name] = Status.done
self.pending_actions.remove(name)
if len(self.dependents[name]) != 0:
for action in self.dependents[name]:
if self.current_action_status[action] == Status.not_started:
self.update_dependencies_status(action)
break
else:
process = subprocess.Popen(command, shell=True, cwd=cwd)
Comment on lines +189 to +208
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of custom logic here, that the executor shouldn't care about. I would suggest adding a "start" method to your Action class.
Eg - what if you have another type of executor class (which does things serially, instead of in parallel). You don't want to duplicate logic in that one.

self.ongoing_subprocesses.append(process)
self.action_name_for_ongoing_subprocess.append(name)
Comment on lines +209 to +210
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of separate lists, you can just use a single dict.

self.action_name_to_ongoing_process[action_name] = process


if any_action_has_no_command:
continue

while True:
any_subprocess_completed = False
for process, action in zip(self.ongoing_subprocesses, self.action_name_for_ongoing_subprocess):
if not process.poll() is None:
any_subprocess_completed = True
break

if any_subprocess_completed:
break
time.sleep(1)

self.ongoing_subprocesses.remove(process)
self.action_name_for_ongoing_subprocess.remove(action)
self.current_action_status[action] = Status.done
self.pending_actions.remove(action)

if len(self.dependents[action]) != 0:
for name in self.dependents[action]:
if self.current_action_status[name] == Status.not_started:
self.update_dependencies_status(name)

return

81 changes: 81 additions & 0 deletions solutions/Abhilasha/Assignment2/btest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import unittest
import os
from os import path
import tempfile
import shutil
import BuildAutomationTool as BAT


class TestBuildAutomationTool(unittest.TestCase):

def setUp(self):

self.root = os.getcwd()
self.test_dir = tempfile.TemporaryDirectory()

def tearDown(self):

shutil.rmtree(self.test_dir)

def test_build_test_all_and_test_clean(self):

with self.test_dir as tmpdirname:
new_root_dir = tmpdirname+os.path.sep+"code"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use os.path.join, instead to directly concatenating.

shutil.copytree(self.root+os.path.sep+"code", new_root_dir)
os.chdir(new_root_dir)
executor = BAT.ActionExecutor(new_root_dir)
executor.execute('build', 'test_all')
self.assertEqual(path.exists('test.o'), True)
self.assertEqual(path.exists('test_sort_bubble.exe'), True)
self.assertEqual(path.exists('test_sort_quick.exe'), True)
self.assertEqual(path.exists('test_sort_merge.exe'), True)
os.chdir(os.path.join("algorithms"))
self.assertEqual(path.exists('sort_bubble.o'), True)
self.assertEqual(path.exists('sort_quick.o'), True)
self.assertEqual(path.exists('sort_merge.o'), True)

os.chdir(new_root_dir)
executor.execute('build', 'clean')
self.assertEqual(path.exists('test.o'), False)
self.assertEqual(path.exists('test_sort_bubble.exe'), False)
self.assertEqual(path.exists('test_sort_quick.exe'), False)
self.assertEqual(path.exists('test_sort_merge.exe'), False)
os.chdir(os.path.join("algorithms"))
self.assertEqual(path.exists('sort_bubble.o'), False)
self.assertEqual(path.exists('sort_quick.o'), False)
self.assertEqual(path.exists('sort_merge.o'), False)
os.chdir(self.root)

def test_sort_merge(self):

with self.test_dir as tmpdirname:
new_root_dir = tmpdirname+os.path.sep+"code"
shutil.copytree(self.root+os.path.sep+"code", new_root_dir)
os.chdir(new_root_dir)
executor = BAT.ActionExecutor(new_root_dir)
executor.execute('build', 'test_sort_merge')
self.assertEqual(path.exists('test.o'), True)
self.assertEqual(path.exists('test_sort_merge.exe'), True)
kaustubh-karkare marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(path.exists('test_sort_bubble.exe'), False)
self.assertEqual(path.exists('test_sort_quick.exe'), False)
os.chdir(os.path.join("algorithms"))
self.assertEqual(path.exists('sort_merge.o'), True)
self.assertEqual(path.exists('sort_bubble.o'), False)
self.assertEqual(path.exists('sort_quick.o'), False)
os.chdir(self.root)

def test_invalid_key(self):

with self.test_dir as tmpdirname:
new_root_dir = tmpdirname+os.path.sep+"code"
shutil.copytree(self.root+os.path.sep+"code", new_root_dir)
os.chdir(new_root_dir)
executor = BAT.ActionExecutor(new_root_dir)
with self.assertRaisesRegex(Exception, 'Command not recognized.'):
executor.execute('build', 'test_sort_selection')
os.chdir(self.root)


if __name__ == '__main__':

unittest.main()
18 changes: 18 additions & 0 deletions solutions/Abhilasha/Assignment2/code/algorithms/build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"name": "clean",
"command": "rm -f *.o"
},
{
"name": "sort_bubble",
"command": "g++ -c sort_bubble.cpp"
},
{
"name": "sort_merge",
"command": "g++ -c sort_merge.cpp"
},
{
"name": "sort_quick",
"command": "g++ -c sort_quick.cpp"
}
]
4 changes: 4 additions & 0 deletions solutions/Abhilasha/Assignment2/code/algorithms/desktop.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[ViewState]
Mode=
Vid=
FolderType=Generic
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <bits/stdc++.h>
using namespace std;
void sort_function(vector<int> &v)
{
cout<<"sort bubble";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <bits/stdc++.h>
using namespace std;
void sort_function(vector<int> &v)
{
cout<<"sort merge";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <bits/stdc++.h>
using namespace std;
void sort_function(vector<int> &v)
{
cout<<"sort quick";
}
30 changes: 30 additions & 0 deletions solutions/Abhilasha/Assignment2/code/build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"name": "clean",
"deps": ["algorithms/clean"],
"command": "rm -f test.o && rm -f test_*.exe"
},
{
"name": "test",
"command": "g++ -std=c++11 -c test.cpp"
},
{
"name": "test_sort_bubble",
"deps": ["test", "algorithms/sort_bubble"],
"command": "g++ test.o algorithms/sort_bubble.o -o test_sort_bubble.exe && ./test_sort_bubble.exe"
},
{
"name": "test_sort_merge",
"deps": ["test", "algorithms/sort_merge"],
"command": "g++ test.o algorithms/sort_merge.o -o test_sort_merge.exe && ./test_sort_merge.exe"
},
{
"name": "test_sort_quick",
"deps": ["test", "algorithms/sort_quick"],
"command": "g++ test.o algorithms/sort_quick.o -o test_sort_quick.exe && ./test_sort_quick.exe"
},
{
"name": "test_all",
"deps": ["test_sort_bubble", "test_sort_merge", "test_sort_quick"]
}
]
Loading